Remove the word middleware (#1067)

* Rename middleware to plugin

first pass; mostly used 'sed', few spots where I manually changed
text.

This still builds a coredns binary.

* fmt error

* Rename AddMiddleware to AddPlugin

* Readd AddMiddleware to remain backwards compat
This commit is contained in:
Miek Gieben
2017-09-14 09:36:06 +01:00
committed by GitHub
parent b984aa4559
commit d8714e64e4
354 changed files with 974 additions and 969 deletions

55
plugin/file/README.md Normal file
View File

@@ -0,0 +1,55 @@
# file
*file* enables serving zone data from an RFC 1035-style master file.
The file plugin is used for an "old-style" DNS server. It serves from a preloaded file that exists
on disk. If the zone file contains signatures (i.e. is signed, i.e. DNSSEC) correct DNSSEC answers
are returned. Only NSEC is supported! If you use this setup *you* are responsible for resigning the
zonefile.
## Syntax
~~~
file DBFILE [ZONES...]
~~~
* **DBFILE** the database file to read and parse. If the path is relative the path from the *root*
directive will be prepended to it.
* **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* plugin.
TSIG key configuration is TODO; directive format for transfer will probably be extended with
TSIG key information, something like `transfer out [ADDRESS...] key [NAME[:ALG]] [BASE64]`
~~~
file DBFILE [ZONES... ] {
transfer to ADDRESS...
no_reload
upstream ADDRESS...
}
~~~
* `transfer` enables zone transfers. It may be specified multiples times. `To` or `from` signals
the direction. **ADDRESS** must be denoted in CIDR notation (127.0.0.1/32 etc.) or just as plain
addresses. The special wildcard `*` means: the entire internet (only valid for 'transfer to').
When an address is specified a notify message will be send whenever the zone is reloaded.
* `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. 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. **ADDRESS** can be an IP
address, and IP:port or a string pointing to a file that is structured as /etc/resolv.conf.
## Examples
Load the `example.org` zone from `example.org.signed` and allow transfers to the internet, but send
notifies to 10.240.1.1
~~~
file example.org.signed example.org {
transfer to *
transfer to 10.240.1.1
}
~~~

24
plugin/file/closest.go Normal file
View File

@@ -0,0 +1,24 @@
package file
import (
"github.com/coredns/coredns/plugin/file/tree"
"github.com/miekg/dns"
)
// ClosestEncloser returns the closest encloser for qname.
func (z *Zone) ClosestEncloser(qname string) (*tree.Elem, bool) {
offset, end := dns.NextLabel(qname, 0)
for !end {
elem, _ := z.Tree.Search(qname)
if elem != nil {
return elem, true
}
qname = qname[offset:]
offset, end = dns.NextLabel(qname, offset)
}
return z.Tree.Search(z.origin)
}

View File

@@ -0,0 +1,38 @@
package file
import (
"strings"
"testing"
)
func TestClosestEncloser(t *testing.T) {
z, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin", 0)
if err != nil {
t.Fatalf("expect no error when reading zone, got %q", err)
}
tests := []struct {
in, out string
}{
{"miek.nl.", "miek.nl."},
{"www.miek.nl.", "www.miek.nl."},
{"blaat.miek.nl.", "miek.nl."},
{"blaat.www.miek.nl.", "www.miek.nl."},
{"www.blaat.miek.nl.", "miek.nl."},
{"blaat.a.miek.nl.", "a.miek.nl."},
}
for _, tc := range tests {
ce, _ := z.ClosestEncloser(tc.in)
if ce == nil {
if z.origin != tc.out {
t.Errorf("Expected ce to be %s for %s, got %s", tc.out, tc.in, ce.Name())
}
continue
}
if ce.Name() != tc.out {
t.Errorf("Expected ce to be %s for %s, got %s", tc.out, tc.in, ce.Name())
}
}
}

124
plugin/file/cname_test.go Normal file
View File

@@ -0,0 +1,124 @@
package file
import (
"strings"
"testing"
"github.com/coredns/coredns/plugin/pkg/dnsrecorder"
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/plugin/test"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
func TestLookupCNAMEChain(t *testing.T) {
name := "example.org."
zone, err := Parse(strings.NewReader(dbExampleCNAME), name, "stdin", 0)
if err != nil {
t.Fatalf("Expected no error when reading zone, got %q", err)
}
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{name: zone}, Names: []string{name}}}
ctx := context.TODO()
for _, tc := range cnameTestCases {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := fm.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("Expected no error, got %v\n", err)
return
}
resp := rec.Msg
test.SortAndCheck(t, resp, tc)
}
}
var cnameTestCases = []test.Case{
{
Qname: "a.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.A("a.example.org. 1800 IN A 127.0.0.1"),
},
},
{
Qname: "www3.example.org.", Qtype: dns.TypeCNAME,
Answer: []dns.RR{
test.CNAME("www3.example.org. 1800 IN CNAME www2.example.org."),
},
},
{
Qname: "dangling.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.CNAME("dangling.example.org. 1800 IN CNAME foo.example.org."),
},
},
{
Qname: "www3.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.A("a.example.org. 1800 IN A 127.0.0.1"),
test.CNAME("www.example.org. 1800 IN CNAME a.example.org."),
test.CNAME("www1.example.org. 1800 IN CNAME www.example.org."),
test.CNAME("www2.example.org. 1800 IN CNAME www1.example.org."),
test.CNAME("www3.example.org. 1800 IN CNAME www2.example.org."),
},
},
}
func TestLookupCNAMEExternal(t *testing.T) {
name := "example.org."
zone, err := Parse(strings.NewReader(dbExampleCNAME), name, "stdin", 0)
if err != nil {
t.Fatalf("Expected no error when reading zone, got %q", err)
}
zone.Proxy = proxy.NewLookup([]string{"8.8.8.8:53"}) // TODO(miek): point to local instance
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{name: zone}, Names: []string{name}}}
ctx := context.TODO()
for _, tc := range exernalTestCases {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := fm.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("Expected no error, got %v\n", err)
return
}
resp := rec.Msg
test.SortAndCheck(t, resp, tc)
}
}
var exernalTestCases = []test.Case{
{
Qname: "external.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.CNAME("external.example.org. 1800 CNAME www.example.net."),
// magic 303 TTL that says: don't check TTL.
test.A("www.example.net. 303 IN A 93.184.216.34"),
},
},
}
const dbExampleCNAME = `
$TTL 30M
$ORIGIN example.org.
@ IN SOA linode.atoom.net. miek.miek.nl. (
1282630057 ; Serial
4H ; Refresh
1H ; Retry
7D ; Expire
4H ) ; Negative Cache TTL
a IN A 127.0.0.1
www3 IN CNAME www2
www2 IN CNAME www1
www1 IN CNAME www
www IN CNAME a
dangling IN CNAME foo
external IN CNAME www.example.net.`

View File

@@ -0,0 +1,207 @@
package file
import (
"strings"
"testing"
"github.com/coredns/coredns/plugin/pkg/dnsrecorder"
"github.com/coredns/coredns/plugin/test"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
var delegationTestCases = []test.Case{
{
Qname: "a.delegated.miek.nl.", Qtype: dns.TypeTXT,
Ns: []dns.RR{
test.NS("delegated.miek.nl. 1800 IN NS a.delegated.miek.nl."),
test.NS("delegated.miek.nl. 1800 IN NS ns-ext.nlnetlabs.nl."),
},
Extra: []dns.RR{
test.A("a.delegated.miek.nl. 1800 IN A 139.162.196.78"),
test.AAAA("a.delegated.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"),
},
},
{
Qname: "delegated.miek.nl.", Qtype: dns.TypeNS,
Answer: []dns.RR{
test.NS("delegated.miek.nl. 1800 IN NS a.delegated.miek.nl."),
test.NS("delegated.miek.nl. 1800 IN NS ns-ext.nlnetlabs.nl."),
},
Extra: []dns.RR{
test.A("a.delegated.miek.nl. 1800 IN A 139.162.196.78"),
test.AAAA("a.delegated.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"),
},
},
{
Qname: "foo.delegated.miek.nl.", Qtype: dns.TypeA,
Ns: []dns.RR{
test.NS("delegated.miek.nl. 1800 IN NS a.delegated.miek.nl."),
test.NS("delegated.miek.nl. 1800 IN NS ns-ext.nlnetlabs.nl."),
},
Extra: []dns.RR{
test.A("a.delegated.miek.nl. 1800 IN A 139.162.196.78"),
test.AAAA("a.delegated.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"),
},
},
{
Qname: "foo.delegated.miek.nl.", Qtype: dns.TypeTXT,
Ns: []dns.RR{
test.NS("delegated.miek.nl. 1800 IN NS a.delegated.miek.nl."),
test.NS("delegated.miek.nl. 1800 IN NS ns-ext.nlnetlabs.nl."),
},
Extra: []dns.RR{
test.A("a.delegated.miek.nl. 1800 IN A 139.162.196.78"),
test.AAAA("a.delegated.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"),
},
},
{
Qname: "miek.nl.", Qtype: dns.TypeSOA,
Answer: []dns.RR{
test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"),
},
Ns: miekAuth,
},
{
Qname: "miek.nl.", Qtype: dns.TypeAAAA,
Ns: []dns.RR{
test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"),
},
},
}
var secureDelegationTestCases = []test.Case{
{
Qname: "a.delegated.example.org.", Qtype: dns.TypeTXT,
Do: true,
Ns: []dns.RR{
test.DS("delegated.example.org. 1800 IN DS 10056 5 1 EE72CABD1927759CDDA92A10DBF431504B9E1F13"),
test.DS("delegated.example.org. 1800 IN DS 10056 5 2 E4B05F87725FA86D9A64F1E53C3D0E6250946599DFE639C45955B0ED416CDDFA"),
test.NS("delegated.example.org. 1800 IN NS a.delegated.example.org."),
test.NS("delegated.example.org. 1800 IN NS ns-ext.nlnetlabs.nl."),
test.RRSIG("delegated.example.org. 1800 IN RRSIG DS 13 3 1800 20161129153240 20161030153240 49035 example.org. rlNNzcUmtbjLSl02ZzQGUbWX75yCUx0Mug1jHtKVqRq1hpPE2S3863tIWSlz+W9wz4o19OI4jbznKKqk+DGKog=="),
},
Extra: []dns.RR{
test.OPT(4096, true),
test.A("a.delegated.example.org. 1800 IN A 139.162.196.78"),
test.AAAA("a.delegated.example.org. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"),
},
},
{
Qname: "delegated.example.org.", Qtype: dns.TypeNS,
Do: true,
Answer: []dns.RR{
test.NS("delegated.example.org. 1800 IN NS a.delegated.example.org."),
test.NS("delegated.example.org. 1800 IN NS ns-ext.nlnetlabs.nl."),
},
Extra: []dns.RR{
test.OPT(4096, true),
test.A("a.delegated.example.org. 1800 IN A 139.162.196.78"),
test.AAAA("a.delegated.example.org. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"),
},
},
{
Qname: "foo.delegated.example.org.", Qtype: dns.TypeA,
Do: true,
Ns: []dns.RR{
test.DS("delegated.example.org. 1800 IN DS 10056 5 1 EE72CABD1927759CDDA92A10DBF431504B9E1F13"),
test.DS("delegated.example.org. 1800 IN DS 10056 5 2 E4B05F87725FA86D9A64F1E53C3D0E6250946599DFE639C45955B0ED416CDDFA"),
test.NS("delegated.example.org. 1800 IN NS a.delegated.example.org."),
test.NS("delegated.example.org. 1800 IN NS ns-ext.nlnetlabs.nl."),
test.RRSIG("delegated.example.org. 1800 IN RRSIG DS 13 3 1800 20161129153240 20161030153240 49035 example.org. rlNNzcUmtbjLSl02ZzQGUbWX75yCUx0Mug1jHtKVqRq1hpPE2S3863tIWSlz+W9wz4o19OI4jbznKKqk+DGKog=="),
},
Extra: []dns.RR{
test.OPT(4096, true),
test.A("a.delegated.example.org. 1800 IN A 139.162.196.78"),
test.AAAA("a.delegated.example.org. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"),
},
},
{
Qname: "foo.delegated.example.org.", Qtype: dns.TypeTXT,
Do: true,
Ns: []dns.RR{
test.DS("delegated.example.org. 1800 IN DS 10056 5 1 EE72CABD1927759CDDA92A10DBF431504B9E1F13"),
test.DS("delegated.example.org. 1800 IN DS 10056 5 2 E4B05F87725FA86D9A64F1E53C3D0E6250946599DFE639C45955B0ED416CDDFA"),
test.NS("delegated.example.org. 1800 IN NS a.delegated.example.org."),
test.NS("delegated.example.org. 1800 IN NS ns-ext.nlnetlabs.nl."),
test.RRSIG("delegated.example.org. 1800 IN RRSIG DS 13 3 1800 20161129153240 20161030153240 49035 example.org. rlNNzcUmtbjLSl02ZzQGUbWX75yCUx0Mug1jHtKVqRq1hpPE2S3863tIWSlz+W9wz4o19OI4jbznKKqk+DGKog=="),
},
Extra: []dns.RR{
test.OPT(4096, true),
test.A("a.delegated.example.org. 1800 IN A 139.162.196.78"),
test.AAAA("a.delegated.example.org. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"),
},
},
}
var miekAuth = []dns.RR{
test.NS("miek.nl. 1800 IN NS ext.ns.whyscream.net."),
test.NS("miek.nl. 1800 IN NS linode.atoom.net."),
test.NS("miek.nl. 1800 IN NS ns-ext.nlnetlabs.nl."),
test.NS("miek.nl. 1800 IN NS omval.tednet.nl."),
}
func TestLookupDelegation(t *testing.T) {
testDelegation(t, dbMiekNLDelegation, testzone, delegationTestCases)
}
func TestLookupSecureDelegation(t *testing.T) {
testDelegation(t, exampleOrgSigned, "example.org.", secureDelegationTestCases)
}
func testDelegation(t *testing.T, z, origin string, testcases []test.Case) {
zone, err := Parse(strings.NewReader(z), origin, "stdin", 0)
if err != nil {
t.Fatalf("Expect no error when reading zone, got %q", err)
}
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{origin: zone}, Names: []string{origin}}}
ctx := context.TODO()
for _, tc := range testcases {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := fm.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("Expected no error, got %q\n", err)
return
}
resp := rec.Msg
test.SortAndCheck(t, resp, tc)
}
}
const dbMiekNLDelegation = `
$TTL 30M
$ORIGIN miek.nl.
@ IN SOA linode.atoom.net. miek.miek.nl. (
1282630057 ; Serial
4H ; Refresh
1H ; Retry
7D ; Expire
4H ) ; Negative Cache TTL
IN NS linode.atoom.net.
IN NS ns-ext.nlnetlabs.nl.
IN NS omval.tednet.nl.
IN NS ext.ns.whyscream.net.
IN MX 1 aspmx.l.google.com.
IN MX 5 alt1.aspmx.l.google.com.
IN MX 5 alt2.aspmx.l.google.com.
IN MX 10 aspmx2.googlemail.com.
IN MX 10 aspmx3.googlemail.com.
delegated IN NS a.delegated
IN NS ns-ext.nlnetlabs.nl.
a.delegated IN TXT "obscured"
IN A 139.162.196.78
IN AAAA 2a01:7e00::f03c:91ff:fef1:6735
a IN A 139.162.196.78
IN AAAA 2a01:7e00::f03c:91ff:fef1:6735
www IN CNAME a
archive IN CNAME a`

44
plugin/file/dname.go Normal file
View File

@@ -0,0 +1,44 @@
package file
import (
"github.com/coredns/coredns/plugin/pkg/dnsutil"
"github.com/miekg/dns"
)
// substituteDNAME performs the DNAME substitution defined by RFC 6672,
// assuming the QTYPE of the query is not DNAME. It returns an empty
// string if there is no match.
func substituteDNAME(qname, owner, target string) string {
if dns.IsSubDomain(owner, qname) && qname != owner {
labels := dns.SplitDomainName(qname)
labels = append(labels[0:len(labels)-dns.CountLabel(owner)], dns.SplitDomainName(target)...)
return dnsutil.Join(labels)
}
return ""
}
// synthesizeCNAME returns a CNAME RR pointing to the resulting name of
// the DNAME substitution. The owner name of the CNAME is the QNAME of
// the query and the TTL is the same as the corresponding DNAME RR.
//
// It returns nil if the DNAME substitution has no match.
func synthesizeCNAME(qname string, d *dns.DNAME) *dns.CNAME {
target := substituteDNAME(qname, d.Header().Name, d.Target)
if target == "" {
return nil
}
r := new(dns.CNAME)
r.Hdr = dns.RR_Header{
Name: qname,
Rrtype: dns.TypeCNAME,
Class: dns.ClassINET,
Ttl: d.Header().Ttl,
}
r.Target = target
return r
}

300
plugin/file/dname_test.go Normal file
View File

@@ -0,0 +1,300 @@
package file
import (
"strings"
"testing"
"github.com/coredns/coredns/plugin/pkg/dnsrecorder"
"github.com/coredns/coredns/plugin/test"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
// RFC 6672, Section 2.2. Assuming QTYPE != DNAME.
var dnameSubstitutionTestCases = []struct {
qname string
owner string
target string
expected string
}{
{"com.", "example.com.", "example.net.", ""},
{"example.com.", "example.com.", "example.net.", ""},
{"a.example.com.", "example.com.", "example.net.", "a.example.net."},
{"a.b.example.com.", "example.com.", "example.net.", "a.b.example.net."},
{"ab.example.com.", "b.example.com.", "example.net.", ""},
{"foo.example.com.", "example.com.", "example.net.", "foo.example.net."},
{"a.x.example.com.", "x.example.com.", "example.net.", "a.example.net."},
{"a.example.com.", "example.com.", "y.example.net.", "a.y.example.net."},
{"cyc.example.com.", "example.com.", "example.com.", "cyc.example.com."},
{"cyc.example.com.", "example.com.", "c.example.com.", "cyc.c.example.com."},
{"shortloop.x.x.", "x.", ".", "shortloop.x."},
{"shortloop.x.", "x.", ".", "shortloop."},
}
func TestDNAMESubstitution(t *testing.T) {
for i, tc := range dnameSubstitutionTestCases {
result := substituteDNAME(tc.qname, tc.owner, tc.target)
if result != tc.expected {
if result == "" {
result = "<no match>"
}
t.Errorf("Case %d: Expected %s -> %s, got %v", i, tc.qname, tc.expected, result)
return
}
}
}
var dnameTestCases = []test.Case{
{
Qname: "dname.miek.nl.", Qtype: dns.TypeDNAME,
Answer: []dns.RR{
test.DNAME("dname.miek.nl. 1800 IN DNAME test.miek.nl."),
},
Ns: miekAuth,
},
{
Qname: "dname.miek.nl.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.A("dname.miek.nl. 1800 IN A 127.0.0.1"),
},
Ns: miekAuth,
},
{
Qname: "dname.miek.nl.", Qtype: dns.TypeMX,
Answer: []dns.RR{},
Ns: []dns.RR{
test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"),
},
},
{
Qname: "a.dname.miek.nl.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.CNAME("a.dname.miek.nl. 1800 IN CNAME a.test.miek.nl."),
test.A("a.test.miek.nl. 1800 IN A 139.162.196.78"),
test.DNAME("dname.miek.nl. 1800 IN DNAME test.miek.nl."),
},
Ns: miekAuth,
},
{
Qname: "www.dname.miek.nl.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.A("a.test.miek.nl. 1800 IN A 139.162.196.78"),
test.DNAME("dname.miek.nl. 1800 IN DNAME test.miek.nl."),
test.CNAME("www.dname.miek.nl. 1800 IN CNAME www.test.miek.nl."),
test.CNAME("www.test.miek.nl. 1800 IN CNAME a.test.miek.nl."),
},
Ns: miekAuth,
},
}
func TestLookupDNAME(t *testing.T) {
zone, err := Parse(strings.NewReader(dbMiekNLDNAME), testzone, "stdin", 0)
if err != nil {
t.Fatalf("Expect no error when reading zone, got %q", err)
}
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: zone}, Names: []string{testzone}}}
ctx := context.TODO()
for _, tc := range dnameTestCases {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := fm.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("Expected no error, got %v\n", err)
return
}
resp := rec.Msg
test.SortAndCheck(t, resp, tc)
}
}
var dnameDnssecTestCases = []test.Case{
{
// We have no auth section, because the test zone does not have nameservers.
Qname: "ns.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.A("ns.example.org. 1800 IN A 127.0.0.1"),
},
},
{
Qname: "dname.example.org.", Qtype: dns.TypeDNAME,
Do: true,
Answer: []dns.RR{
test.DNAME("dname.example.org. 1800 IN DNAME test.example.org."),
test.RRSIG("dname.example.org. 1800 IN RRSIG DNAME 5 3 1800 20170702091734 20170602091734 54282 example.org. HvXtiBM="),
},
Extra: []dns.RR{test.OPT(4096, true)},
},
{
Qname: "a.dname.example.org.", Qtype: dns.TypeA,
Do: true,
Answer: []dns.RR{
test.CNAME("a.dname.example.org. 1800 IN CNAME a.test.example.org."),
test.DNAME("dname.example.org. 1800 IN DNAME test.example.org."),
test.RRSIG("dname.example.org. 1800 IN RRSIG DNAME 5 3 1800 20170702091734 20170602091734 54282 example.org. HvXtiBM="),
},
Extra: []dns.RR{test.OPT(4096, true)},
},
}
func TestLookupDNAMEDNSSEC(t *testing.T) {
zone, err := Parse(strings.NewReader(dbExampleDNAMESigned), testzone, "stdin", 0)
if err != nil {
t.Fatalf("Expect no error when reading zone, got %q", err)
}
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{"example.org.": zone}, Names: []string{"example.org."}}}
ctx := context.TODO()
for _, tc := range dnameDnssecTestCases {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := fm.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("Expected no error, got %v\n", err)
return
}
resp := rec.Msg
test.SortAndCheck(t, resp, tc)
}
}
const dbMiekNLDNAME = `
$TTL 30M
$ORIGIN miek.nl.
@ IN SOA linode.atoom.net. miek.miek.nl. (
1282630057 ; Serial
4H ; Refresh
1H ; Retry
7D ; Expire
4H ) ; Negative Cache TTL
IN NS linode.atoom.net.
IN NS ns-ext.nlnetlabs.nl.
IN NS omval.tednet.nl.
IN NS ext.ns.whyscream.net.
test IN MX 1 aspmx.l.google.com.
IN MX 5 alt1.aspmx.l.google.com.
IN MX 5 alt2.aspmx.l.google.com.
IN MX 10 aspmx2.googlemail.com.
IN MX 10 aspmx3.googlemail.com.
a.test IN A 139.162.196.78
IN AAAA 2a01:7e00::f03c:91ff:fef1:6735
www.test IN CNAME a.test
dname IN DNAME test
dname IN A 127.0.0.1
a.dname IN A 127.0.0.1
`
const dbExampleDNAMESigned = `
; File written on Fri Jun 2 10:17:34 2017
; dnssec_signzone version 9.10.3-P4-Debian
example.org. 1800 IN SOA a.example.org. b.example.org. (
1282630057 ; serial
14400 ; refresh (4 hours)
3600 ; retry (1 hour)
604800 ; expire (1 week)
14400 ; minimum (4 hours)
)
1800 RRSIG SOA 5 2 1800 (
20170702091734 20170602091734 54282 example.org.
mr5eQtFs1GubgwaCcqrpiF6Cgi822OkESPeV
X0OJYq3JzthJjHw8TfYAJWQ2yGqhlePHir9h
FT/uFZdYyytHq+qgIUbJ9IVCrq0gZISZdHML
Ry1DNffMR9CpD77KocOAUABfopcvH/3UGOHn
TFxkAr447zPaaoC68JYGxYLfZk8= )
1800 NS ns.example.org.
1800 RRSIG NS 5 2 1800 (
20170702091734 20170602091734 54282 example.org.
McM4UdMxkscVQkJnnEbdqwyjpPgq5a/EuOLA
r2MvG43/cwOaWULiZoNzLi5Rjzhf+GTeVTan
jw6EsL3gEuYI1nznwlLQ04/G0XAHjbq5VvJc
rlscBD+dzf774yfaTjRNoeo2xTem6S7nyYPW
Y+1f6xkrsQPLYJfZ6VZ9QqyupBw= )
14400 NSEC dname.example.org. NS SOA RRSIG NSEC DNSKEY
14400 RRSIG NSEC 5 2 14400 (
20170702091734 20170602091734 54282 example.org.
VT+IbjDFajM0doMKFipdX3+UXfCn3iHIxg5x
LElp4Q/YddTbX+6tZf53+EO+G8Kye3JDLwEl
o8VceijNeF3igZ+LiZuXCei5Qg/TJ7IAUnAO
xd85IWwEYwyKkKd6Z2kXbAN2pdcHE8EmboQd
wfTr9oyWhpZk1Z+pN8vdejPrG0M= )
1800 DNSKEY 256 3 5 (
AwEAAczLlmTk5bMXUzpBo/Jta6MWSZYy3Nfw
gz8t/pkfSh4IlFF6vyXZhEqCeQsCBdD7ltkD
h5qd4A+nFrYOMwsi5XIjoHMlJN15xwFS9EgS
ZrZmuxePIEiYB5KccEf9JQMgM1t07Iu1FnrY
02OuAqGWcO4tuyTLaK3QP4MLQOfAgKqf
) ; ZSK; alg = RSASHA1; key id = 54282
1800 RRSIG DNSKEY 5 2 1800 (
20170702091734 20170602091734 54282 example.org.
MBgSRtZ6idJblLIHxZWpWL/1oqIwImb1mkl7
hDFxqV6Hw19yLX06P7gcJEWiisdZBkVEfcOK
LeMJly05vgKfrMzLgIu2Ry4bL8AMKc8NMXBG
b1VDCEBW69P2omogj2KnORHDCZQr/BX9+wBU
5rIMTTKlMSI5sT6ecJHHEymtiac= )
dname.example.org. 1800 IN A 127.0.0.1
1800 RRSIG A 5 3 1800 (
20170702091734 20170602091734 54282 example.org.
LPCK2nLyDdGwvmzGLkUO2atEUjoc+aEspkC3
keZCdXZaLnAwBH7dNAjvvXzzy0WrgWeiyDb4
+rJ2N0oaKEZicM4QQDHKhugJblKbU5G4qTey
LSEaV3vvQnzGd0S6dCqnwfPj9czagFN7Zlf5
DmLtdxx0aiDPCUpqT0+H/vuGPfk= )
1800 DNAME test.example.org.
1800 RRSIG DNAME 5 3 1800 (
20170702091734 20170602091734 54282 example.org.
HvX79T1flWJ8H9/1XZjX6gz8rP/o2jbfPXJ9
vC7ids/ZJilSReabLru4DCqcw1IV2DM/CZdE
tBnED/T2PJXvMut9tnYMrz+ZFPxoV6XyA3Z7
bok3B0OuxizzAN2EXdol04VdbMHoWUzjQCzi
0Ri12zLGRPzDepZ7FolgD+JtiBM= )
14400 NSEC a.dname.example.org. A DNAME RRSIG NSEC
14400 RRSIG NSEC 5 3 14400 (
20170702091734 20170602091734 54282 example.org.
U3ZPYMUBJl3wF2SazQv/kBf6ec0CH+7n0Hr9
w6lBKkiXz7P9WQzJDVnTHEZOrbDI6UetFGyC
6qcaADCASZ9Wxc+riyK1Hl4ox+Y/CHJ97WHy
oS2X//vEf6qmbHQXin0WQtFdU/VCRYF40X5v
8VfqOmrr8iKiEqXND8XNVf58mTw= )
a.dname.example.org. 1800 IN A 127.0.0.1
1800 RRSIG A 5 4 1800 (
20170702091734 20170602091734 54282 example.org.
y7RHBWZwli8SJQ4BgTmdXmYS3KGHZ7AitJCx
zXFksMQtNoOfVEQBwnFqjAb8ezcV5u92h1gN
i1EcuxCFiElML1XFT8dK2GnlPAga9w3oIwd5
wzW/YHcnR0P9lF56Sl7RoIt6+jJqOdRfixS6
TDoLoXsNbOxQ+qV3B8pU2Tam204= )
14400 NSEC ns.example.org. A RRSIG NSEC
14400 RRSIG NSEC 5 4 14400 (
20170702091734 20170602091734 54282 example.org.
Tmu27q3+xfONSZZtZLhejBUVtEw+83ZU1AFb
Rsxctjry/x5r2JSxw/sgSAExxX/7tx/okZ8J
oJqtChpsr91Kiw3eEBgINi2lCYIpMJlW4cWz
8bYlHfR81VsKYgy/cRgrq1RRvBoJnw+nwSty
mKPIvUtt67LAvLxJheSCEMZLCKI= )
ns.example.org. 1800 IN A 127.0.0.1
1800 RRSIG A 5 3 1800 (
20170702091734 20170602091734 54282 example.org.
mhi1SGaaAt+ndQEg5uKWKCH0HMzaqh/9dUK3
p2wWMBrLbTZrcWyz10zRnvehicXDCasbBrer
ZpDQnz5AgxYYBURvdPfUzx1XbNuRJRE4l5PN
CEUTlTWcqCXnlSoPKEJE5HRf7v0xg2BrBUfM
4mZnW2bFLwjrRQ5mm/mAmHmTROk= )
14400 NSEC example.org. A RRSIG NSEC
14400 RRSIG NSEC 5 3 14400 (
20170702091734 20170602091734 54282 example.org.
loHcdjX+NIWLAkUDfPSy2371wrfUvrBQTfMO
17eO2Y9E/6PE935NF5bjQtZBRRghyxzrFJhm
vY1Ad5ZTb+NLHvdSWbJQJog+eCc7QWp64WzR
RXpMdvaE6ZDwalWldLjC3h8QDywDoFdndoRY
eHOsmTvvtWWqtO6Fa5A8gmHT5HA= )
`

358
plugin/file/dnssec_test.go Normal file
View File

@@ -0,0 +1,358 @@
package file
import (
"strings"
"testing"
"github.com/coredns/coredns/plugin/pkg/dnsrecorder"
"github.com/coredns/coredns/plugin/test"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
var dnssecTestCases = []test.Case{
{
Qname: "miek.nl.", Qtype: dns.TypeSOA, Do: true,
Answer: []dns.RR{
test.RRSIG("miek.nl. 1800 IN RRSIG SOA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. FIrzy07acBbtyQczy1dc="),
test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"),
},
Ns: auth,
Extra: []dns.RR{test.OPT(4096, true)},
},
{
Qname: "miek.nl.", Qtype: dns.TypeAAAA, Do: true,
Answer: []dns.RR{
test.AAAA("miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"),
test.RRSIG("miek.nl. 1800 IN RRSIG AAAA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. SsRT="),
},
Ns: auth,
Extra: []dns.RR{test.OPT(4096, true)},
},
{
Qname: "miek.nl.", Qtype: dns.TypeNS, Do: true,
Answer: []dns.RR{
test.NS("miek.nl. 1800 IN NS ext.ns.whyscream.net."),
test.NS("miek.nl. 1800 IN NS linode.atoom.net."),
test.NS("miek.nl. 1800 IN NS ns-ext.nlnetlabs.nl."),
test.NS("miek.nl. 1800 IN NS omval.tednet.nl."),
test.RRSIG("miek.nl. 1800 IN RRSIG NS 8 2 1800 20160426031301 20160327031301 12051 miek.nl. ZLtsQhwaz+lHfNpztFoR1Vxs="),
},
Extra: []dns.RR{test.OPT(4096, true)},
},
{
Qname: "miek.nl.", Qtype: dns.TypeMX, Do: true,
Answer: []dns.RR{
test.MX("miek.nl. 1800 IN MX 1 aspmx.l.google.com."),
test.MX("miek.nl. 1800 IN MX 10 aspmx2.googlemail.com."),
test.MX("miek.nl. 1800 IN MX 10 aspmx3.googlemail.com."),
test.MX("miek.nl. 1800 IN MX 5 alt1.aspmx.l.google.com."),
test.MX("miek.nl. 1800 IN MX 5 alt2.aspmx.l.google.com."),
test.RRSIG("miek.nl. 1800 IN RRSIG MX 8 2 1800 20160426031301 20160327031301 12051 miek.nl. kLqG+iOr="),
},
Ns: auth,
Extra: []dns.RR{test.OPT(4096, true)},
},
{
Qname: "www.miek.nl.", Qtype: dns.TypeA, Do: true,
Answer: []dns.RR{
test.A("a.miek.nl. 1800 IN A 139.162.196.78"),
test.RRSIG("a.miek.nl. 1800 IN RRSIG A 8 3 1800 20160426031301 20160327031301 12051 miek.nl. lxLotCjWZ3kihTxk="),
test.CNAME("www.miek.nl. 1800 IN CNAME a.miek.nl."),
test.RRSIG("www.miek.nl. 1800 RRSIG CNAME 8 3 1800 20160426031301 20160327031301 12051 miek.nl. NVZmMJaypS+wDL2Lar4Zw1zF"),
},
Ns: auth,
Extra: []dns.RR{
test.OPT(4096, true),
},
},
{
// NoData
Qname: "a.miek.nl.", Qtype: dns.TypeSRV, Do: true,
Ns: []dns.RR{
test.NSEC("a.miek.nl. 14400 IN NSEC archive.miek.nl. A AAAA RRSIG NSEC"),
test.RRSIG("a.miek.nl. 14400 IN RRSIG NSEC 8 3 14400 20160426031301 20160327031301 12051 miek.nl. GqnF6cutipmSHEao="),
test.RRSIG("miek.nl. 1800 IN RRSIG SOA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. FIrzy07acBbtyQczy1dc="),
test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"),
},
Extra: []dns.RR{test.OPT(4096, true)},
},
{
Qname: "b.miek.nl.", Qtype: dns.TypeA, Do: true,
Rcode: dns.RcodeNameError,
Ns: []dns.RR{
test.NSEC("archive.miek.nl. 14400 IN NSEC go.dns.miek.nl. CNAME RRSIG NSEC"),
test.RRSIG("archive.miek.nl. 14400 IN RRSIG NSEC 8 3 14400 20160426031301 20160327031301 12051 miek.nl. jEpx8lcp4do5fWXg="),
test.NSEC("miek.nl. 14400 IN NSEC a.miek.nl. A NS SOA MX AAAA RRSIG NSEC DNSKEY"),
test.RRSIG("miek.nl. 14400 IN RRSIG NSEC 8 2 14400 20160426031301 20160327031301 12051 miek.nl. mFfc3r/9PSC1H6oSpdC"),
test.RRSIG("miek.nl. 1800 IN RRSIG SOA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. FIrzy07acBbtyQczy1dc="),
test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"),
},
Extra: []dns.RR{test.OPT(4096, true)},
},
{
Qname: "b.blaat.miek.nl.", Qtype: dns.TypeA, Do: true,
Rcode: dns.RcodeNameError,
Ns: []dns.RR{
test.NSEC("archive.miek.nl. 14400 IN NSEC go.dns.miek.nl. CNAME RRSIG NSEC"),
test.RRSIG("archive.miek.nl. 14400 IN RRSIG NSEC 8 3 14400 20160426031301 20160327031301 12051 miek.nl. jEpx8lcp4do5fWXg="),
test.NSEC("miek.nl. 14400 IN NSEC a.miek.nl. A NS SOA MX AAAA RRSIG NSEC DNSKEY"),
test.RRSIG("miek.nl. 14400 IN RRSIG NSEC 8 2 14400 20160426031301 20160327031301 12051 miek.nl. mFfc3r/9PSC1H6oSpdC"),
test.RRSIG("miek.nl. 1800 IN RRSIG SOA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. FIrzy07acBbtyQczy1dc="),
test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"),
},
Extra: []dns.RR{test.OPT(4096, true)},
},
{
Qname: "b.a.miek.nl.", Qtype: dns.TypeA, Do: true,
Rcode: dns.RcodeNameError,
Ns: []dns.RR{
// dedupped NSEC, because 1 nsec tells all
test.NSEC("a.miek.nl. 14400 IN NSEC archive.miek.nl. A AAAA RRSIG NSEC"),
test.RRSIG("a.miek.nl. 14400 IN RRSIG NSEC 8 3 14400 20160426031301 20160327031301 12051 miek.nl. GqnF6cut/RRGPQ1QGQE1ipmSHEao="),
test.RRSIG("miek.nl. 1800 IN RRSIG SOA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. FIrzy07acBbtyQczy1dc="),
test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"),
},
Extra: []dns.RR{test.OPT(4096, true)},
},
}
var auth = []dns.RR{
test.NS("miek.nl. 1800 IN NS ext.ns.whyscream.net."),
test.NS("miek.nl. 1800 IN NS linode.atoom.net."),
test.NS("miek.nl. 1800 IN NS ns-ext.nlnetlabs.nl."),
test.NS("miek.nl. 1800 IN NS omval.tednet.nl."),
test.RRSIG("miek.nl. 1800 IN RRSIG NS 8 2 1800 20160426031301 20160327031301 12051 miek.nl. ZLtsQhwazbqSpztFoR1Vxs="),
}
func TestLookupDNSSEC(t *testing.T) {
zone, err := Parse(strings.NewReader(dbMiekNLSigned), testzone, "stdin", 0)
if err != nil {
t.Fatalf("Expected no error when reading zone, got %q", err)
}
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: zone}, Names: []string{testzone}}}
ctx := context.TODO()
for _, tc := range dnssecTestCases {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := fm.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("Expected no error, got %v\n", err)
return
}
resp := rec.Msg
test.SortAndCheck(t, resp, tc)
}
}
func BenchmarkFileLookupDNSSEC(b *testing.B) {
zone, err := Parse(strings.NewReader(dbMiekNLSigned), testzone, "stdin", 0)
if err != nil {
return
}
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: zone}, Names: []string{testzone}}}
ctx := context.TODO()
rec := dnsrecorder.New(&test.ResponseWriter{})
tc := test.Case{
Qname: "b.miek.nl.", Qtype: dns.TypeA, Do: true,
Rcode: dns.RcodeNameError,
Ns: []dns.RR{
test.NSEC("archive.miek.nl. 14400 IN NSEC go.dns.miek.nl. CNAME RRSIG NSEC"),
test.RRSIG("archive.miek.nl. 14400 IN RRSIG NSEC 8 3 14400 20160426031301 20160327031301 12051 miek.nl. jEpx8lcp4do5fWXg="),
test.NSEC("miek.nl. 14400 IN NSEC a.miek.nl. A NS SOA MX AAAA RRSIG NSEC DNSKEY"),
test.RRSIG("miek.nl. 14400 IN RRSIG NSEC 8 2 14400 20160426031301 20160327031301 12051 miek.nl. mFfc3r/9PSC1H6oSpdC"),
test.RRSIG("miek.nl. 1800 IN RRSIG SOA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. FIrzy07acBbtyQczy1dc="),
test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"),
},
}
m := tc.Msg()
b.ResetTimer()
for i := 0; i < b.N; i++ {
fm.ServeDNS(ctx, rec, m)
}
}
const dbMiekNLSigned = `
; File written on Sun Mar 27 04:13:01 2016
; dnssec_signzone version 9.10.3-P4-Ubuntu
miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. (
1459051981 ; serial
14400 ; refresh (4 hours)
3600 ; retry (1 hour)
604800 ; expire (1 week)
14400 ; minimum (4 hours)
)
1800 RRSIG SOA 8 2 1800 (
20160426031301 20160327031301 12051 miek.nl.
FIrzy07acBzrf6kNW13Ypmq/ahojoMqOj0qJ
ixTevTvwOEcVuw9GlJoYIHTYg+hm1sZHtx9K
RiVmYsm8SHKsJA1WzixtT4K7vQvM+T+qbeOJ
xA6YTivKUcGRWRXQlOTUAlHS/KqBEfmxKgRS
68G4oOEClFDSJKh7RbtyQczy1dc= )
1800 NS ext.ns.whyscream.net.
1800 NS omval.tednet.nl.
1800 NS linode.atoom.net.
1800 NS ns-ext.nlnetlabs.nl.
1800 RRSIG NS 8 2 1800 (
20160426031301 20160327031301 12051 miek.nl.
ZLtsQhwaz+CwrgzgFiEAqbqS/JH65MYjziA3
6EXwlGDy41lcfGm71PpxA7cDzFhWNkJNk4QF
q48wtpP4IGPPpHbnJHKDUXj6se7S+ylAGbS+
VgVJ4YaVcE6xA9ZVhVpz8CSSjeH34vmqq9xj
zmFjofuDvraZflHfNpztFoR1Vxs= )
1800 A 139.162.196.78
1800 RRSIG A 8 2 1800 (
20160426031301 20160327031301 12051 miek.nl.
hl+6Q075tsCkxIqbop8zZ6U8rlFvooz7Izzx
MgCZYVLcg75El28EXKIhBfRb1dPaKbd+v+AD
wrJMHL131pY5sU2Ly05K+7CqmmyaXgDaVsKS
rSw/TbhGDIItBemeseeuXGAKAbY2+gE7kNN9
mZoQ9hRB3SrxE2jhctv66DzYYQQ= )
1800 MX 1 aspmx.l.google.com.
1800 MX 5 alt1.aspmx.l.google.com.
1800 MX 5 alt2.aspmx.l.google.com.
1800 MX 10 aspmx2.googlemail.com.
1800 MX 10 aspmx3.googlemail.com.
1800 RRSIG MX 8 2 1800 (
20160426031301 20160327031301 12051 miek.nl.
kLqG+iOrKSzms1H9Et9me8Zts1rbyeCFSVQD
G9is/u6ec3Lqg2vwJddf/yRsjVpVgadWSAkc
GSDuD2dK8oBeP24axWc3Z1OY2gdMI7w+PKWT
Z+pjHVjbjM47Ii/a6jk5SYeOwpGMsdEwhtTP
vk2O2WGljifqV3uE7GshF5WNR10= )
1800 AAAA 2a01:7e00::f03c:91ff:fef1:6735
1800 RRSIG AAAA 8 2 1800 (
20160426031301 20160327031301 12051 miek.nl.
SsRTHytW4YTAuHovHQgfIMhNwMtMp4gaAU/Z
lgTO+IkBb9y9F8uHrf25gG6RqA1bnGV/gezV
NU5negXm50bf1BNcyn3aCwEbA0rCGYIL+nLJ
szlBVbBu6me/Ym9bbJlfgfHRDfsVy2ZkNL+B
jfNQtGCSDoJwshjcqJlfIVSardo= )
14400 NSEC a.miek.nl. A NS SOA MX AAAA RRSIG NSEC DNSKEY
14400 RRSIG NSEC 8 2 14400 (
20160426031301 20160327031301 12051 miek.nl.
mFfc3r/9PSC1H6oSpdC+FDy/Iu02W2Tf0x+b
n6Lpe1gCC1uvcSUrrmBNlyAWRr5Zm+ZXssEb
cKddRGiu/5sf0bUWrs4tqokL/HUl10X/sBxb
HfwNAeD7R7+CkpMv67li5AhsDgmQzpX2r3P6
/6oZyLvODGobysbmzeWM6ckE8IE= )
1800 DNSKEY 256 3 8 (
AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6
E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5EC
IoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb
2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXH
Py7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz
) ; ZSK; alg = RSASHA256; key id = 12051
1800 DNSKEY 257 3 8 (
AwEAAcWdjBl4W4wh/hPxMDcBytmNCvEngIgB
9Ut3C2+QI0oVz78/WK9KPoQF7B74JQ/mjO4f
vIncBmPp6mFNxs9/WQX0IXf7oKviEVOXLjct
R4D1KQLX0wprvtUIsQFIGdXaO6suTT5eDbSd
6tTwu5xIkGkDmQhhH8OQydoEuCwV245ZwF/8
AIsqBYDNQtQ6zhd6jDC+uZJXg/9LuPOxFHbi
MTjp6j3CCW0kHbfM/YHZErWWtjPj3U3Z7knQ
SIm5PO5FRKBEYDdr5UxWJ/1/20SrzI3iztvP
wHDsA2rdHm/4YRzq7CvG4N0t9ac/T0a0Sxba
/BUX2UVPWaIVBdTRBtgHi0s=
) ; KSK; alg = RSASHA256; key id = 33694
1800 RRSIG DNSKEY 8 2 1800 (
20160426031301 20160327031301 12051 miek.nl.
o/D6o8+/bNGQyyRvwZ2hM0BJ+3HirvNjZoko
yGhGe9sPSrYU39WF3JVIQvNJFK6W3/iwlKir
TPOeYlN6QilnztFq1vpCxwj2kxJaIJhZecig
LsKxY/fOHwZlIbBLZZadQG6JoGRLHnImSzpf
xtyVaXQtfnJFC07HHt9np3kICfE= )
1800 RRSIG DNSKEY 8 2 1800 (
20160426031301 20160327031301 33694 miek.nl.
Ak/mbbQVQV+nUgw5Sw/c+TSoYqIwbLARzuNE
QJvJNoRR4tKVOY6qSxQv+j5S7vzyORZ+yeDp
NlEa1T9kxZVBMABoOtLX5kRqZncgijuH8fxb
L57Sv2IzINI9+DOcy9Q9p9ygtwYzQKrYoNi1
0hwHi6emGkVG2gGghruMinwOJASGgQy487Yd
eIpcEKJRw73nxd2le/4/Vafy+mBpKWOczfYi
5m9MSSxcK56NFYjPG7TvdIw0m70F/smY9KBP
pGWEdzRQDlqfZ4fpDaTAFGyRX0mPFzMbs1DD
3hQ4LHUSi/NgQakdH9eF42EVEDeL4cI69K98
6NNk6X9TRslO694HKw== )
a.miek.nl. 1800 IN A 139.162.196.78
1800 RRSIG A 8 3 1800 (
20160426031301 20160327031301 12051 miek.nl.
lxLotCjWZ3kikNNcePu6HOCqMHDINKFRJRD8
laz2KQ9DKtgXPdnRw5RJvVITSj8GUVzw1ec1
CYVEKu/eMw/rc953Zns528QBypGPeMNLe2vu
C6a6UhZnGHA48dSd9EX33eSJs0MP9xsC9csv
LGdzYmv++eslkKxkhSOk2j/hTxk= )
1800 AAAA 2a01:7e00::f03c:91ff:fef1:6735
1800 RRSIG AAAA 8 3 1800 (
20160426031301 20160327031301 12051 miek.nl.
ji3QMlaUzlK85ppB5Pc+y2WnfqOi6qrm6dm1
bXgsEov/5UV1Lmcv8+Y5NBbTbBlXGlWcpqNp
uWpf9z3lbguDWznpnasN2MM8t7yxo/Cr7WRf
QCzui7ewpWiA5hq7j0kVbM4nnDc6cO+U93hO
mMhVbeVI70HM2m0HaHkziEyzVZk= )
14400 NSEC archive.miek.nl. A AAAA RRSIG NSEC
14400 RRSIG NSEC 8 3 14400 (
20160426031301 20160327031301 12051 miek.nl.
GqnF6cut/KCxbnJj27MCjjVGkjObV0hLhHOP
E1/GXAUTEKG6BWxJq8hidS3p/yrOmP5PEL9T
4FjBp0/REdVmGpuLaiHyMselES82p/uMMdY5
QqRM6LHhZdO1zsRbyzOZbm5MsW6GR7K2kHlX
9TdBIULiRRGPQ1QGQE1ipmSHEao= )
archive.miek.nl. 1800 IN CNAME a.miek.nl.
1800 RRSIG CNAME 8 3 1800 (
20160426031301 20160327031301 12051 miek.nl.
s4zVJiDrVuUiUFr8CNQLuXYYfpqpl8rovL50
BYsub/xK756NENiOTAOjYH6KYg7RSzsygJjV
YQwXolZly2/KXAr48SCtxzkGFxLexxiKcFaj
vm7ZDl7Btoa5l68qmBcxOX5E/W0IKITi4PNK
mhBs7dlaf0IbPGNgMxae72RosxM= )
14400 NSEC go.dns.miek.nl. CNAME RRSIG NSEC
14400 RRSIG NSEC 8 3 14400 (
20160426031301 20160327031301 12051 miek.nl.
jEp7LsoK++/PRFh2HieLzasA1jXBpp90NyDf
RfpfOxdM69yRKfvXMc2bazIiMuDhxht79dGI
Gj02cn1cvX60SlaHkeFtqTdJcHdK9rbI65EK
YHFZFzGh9XVnuMJKpUsm/xS1dnUSAnXN8q+0
xBlUDlQpsAFv/cx8lcp4do5fWXg= )
go.dns.miek.nl. 1800 IN TXT "Hello!"
1800 RRSIG TXT 8 4 1800 (
20160426031301 20160327031301 12051 miek.nl.
O0uo1NsXTq2TTfgOmGbHQQEchrcpllaDAMMX
dTDizw3t+vZ5SR32qJ8W7y6VXLgUqJgcdRxS
Fou1pp+t5juRZSQ0LKgxMpZAgHorkzPvRf1b
E9eBKrDSuLGagsQRwHeldFGFgsXtCbf07vVH
zoKR8ynuG4/cAoY0JzMhCts+56U= )
14400 NSEC www.miek.nl. TXT RRSIG NSEC
14400 RRSIG NSEC 8 4 14400 (
20160426031301 20160327031301 12051 miek.nl.
BW6qo7kYe3Z+Y0ebaVTWTy1c3bpdf8WUEoXq
WDQxLDEj2fFiuEBDaSN5lTWRg3wj8kZmr6Uk
LvX0P29lbATFarIgkyiAdbOEdaf88nMfqBW8
z2T5xrPQcN0F13uehmv395yAJs4tebRxErMl
KdkVF0dskaDvw8Wo3YgjHUf6TXM= )
www.miek.nl. 1800 IN CNAME a.miek.nl.
1800 RRSIG CNAME 8 3 1800 (
20160426031301 20160327031301 12051 miek.nl.
MiQQh2lScoNiNVZmMJaypS+wDL2Lar4Zw1zF
Uo4tL16BfQOt7yl8gXdAH2JMFqoKAoIdM2K6
XwFOwKTOGSW0oNCOcaE7ts+1Z1U0H3O2tHfq
FAzfg1s9pQ5zxk8J/bJgkVIkw2/cyB0y1/PK
EmIqvChBSb4NchTuMCSqo63LJM8= )
14400 NSEC miek.nl. CNAME RRSIG NSEC
14400 RRSIG NSEC 8 3 14400 (
20160426031301 20160327031301 12051 miek.nl.
OPPZ8iaUPrVKEP4cqeCiiv1WLRAY30GRIhc/
me0gBwFkbmTEnvB+rUp831OJZDZBNKv4QdZj
Uyc26wKUOQeUyMJqv4IRDgxH7nq9GB5JRjYZ
IVxtGD1aqWLXz+8aMaf9ARJjtYUd3K4lt8Wz
LbJSo5Wdq7GOWqhgkY5n3XD0/FA= )`

145
plugin/file/dnssex_test.go Normal file
View File

@@ -0,0 +1,145 @@
package file
const dbDnssexNLSigned = `
; File written on Tue Mar 29 21:02:24 2016
; dnssec_signzone version 9.10.3-P4-Ubuntu
dnssex.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. (
1459281744 ; serial
14400 ; refresh (4 hours)
3600 ; retry (1 hour)
604800 ; expire (1 week)
14400 ; minimum (4 hours)
)
1800 RRSIG SOA 8 2 1800 (
20160428190224 20160329190224 14460 dnssex.nl.
CA/Y3m9hCOiKC/8ieSOv8SeP964BUdG/8MC3
WtKljUosK9Z9bBGrVizDjjqgq++lyH8BZJcT
aabAsERs4xj5PRtcxicwQXZACX5VYjXHQeZm
CyytFU5wq2gcXSmvUH86zZzftx3RGPvn1aOo
TlcvoC3iF8fYUCpROlUS0YR8Cdw= )
1800 NS omval.tednet.nl.
1800 NS linode.atoom.net.
1800 NS ns-ext.nlnetlabs.nl.
1800 RRSIG NS 8 2 1800 (
20160428190224 20160329190224 14460 dnssex.nl.
dLIeEvP86jj5nd3orv9bH7hTvkblF4Na0sbl
k6fJA6ha+FPN1d6Pig3NNEEVQ/+wlOp/JTs2
v07L7roEEUCbBprI8gMSld2gFDwNLW3DAB4M
WD/oayYdAnumekcLzhgvWixTABjWAGRTGQsP
sVDFXsGMf9TGGC9FEomgkCVeNC0= )
1800 A 139.162.196.78
1800 RRSIG A 8 2 1800 (
20160428190224 20160329190224 14460 dnssex.nl.
LKJKLzPiSEDWOLAag2YpfD5EJCuDcEAJu+FZ
Xy+4VyOv9YvRHCTL4vbrevOo5+XymY2RxU1q
j+6leR/Fe7nlreSj2wzAAk2bIYn4m6r7hqeO
aKZsUFfpX8cNcFtGEywfHndCPELbRxFeEziP
utqHFLPNMX5nYCpS28w4oJ5sAnM= )
1800 TXT "Doing It Safe Is Better"
1800 RRSIG TXT 8 2 1800 (
20160428190224 20160329190224 14460 dnssex.nl.
f6S+DUfJK1UYdOb3AHgUXzFTTtu+yLp/Fv7S
Hv0CAGhXAVw+nBbK719igFvBtObS33WKwzxD
1pQNMaJcS6zeevtD+4PKB1KDC4fyJffeEZT6
E30jGR8Y29/xA+Fa4lqDNnj9zP3b8TiABCle
ascY5abkgWCALLocFAzFJQ/27YQ= )
1800 AAAA 2a01:7e00::f03c:91ff:fef1:6735
1800 RRSIG AAAA 8 2 1800 (
20160428190224 20160329190224 14460 dnssex.nl.
PWcPSawEUBAfCuv0liEOQ8RYe7tfNW4rubIJ
LE+dbrub1DUer3cWrDoCYFtOufvcbkYJQ2CQ
AGjJmAQ5J2aqYDOPMrKa615V0KT3ifbZJcGC
gkIic4U/EXjaQpRoLdDzR9MyVXOmbA6sKYzj
ju1cNkLqM8D7Uunjl4pIr6rdSFo= )
14400 NSEC *.dnssex.nl. A NS SOA TXT AAAA RRSIG NSEC DNSKEY
14400 RRSIG NSEC 8 2 14400 (
20160428190224 20160329190224 14460 dnssex.nl.
oIvM6JZIlNc1aNKGTxv58ApSnDr1nDPPgnD9
9oJZRIn7eb5WnpeDz2H3z5+x6Bhlp5hJJaUp
KJ3Ss6Jg/IDnrmIvKmgq6L6gHj1Y1IiHmmU8
VeZTRzdTsDx/27OsN23roIvsytjveNSEMfIm
iLZ23x5kg1kBdJ9p3xjYHm5lR+8= )
1800 DNSKEY 256 3 8 (
AwEAAazSO6uvLPEVknDA8yxjFe8nnAMU7txp
wb19k55hQ81WV3G4bpBM1NdN6sbYHrkXaTNx
2bQWAkvX6pz0XFx3z/MPhW+vkakIWFYpyQ7R
AT5LIJfToVfiCDiyhhF0zVobKBInO9eoGjd9
BAW3TUt+LmNAO/Ak5D5BX7R3CuA7v9k7
) ; ZSK; alg = RSASHA256; key id = 14460
1800 DNSKEY 257 3 8 (
AwEAAbyeaV9zg0IqdtgYoqK5jJ239anzwG2i
gvH1DxSazLyaoNvEkCIvPgMLW/JWfy7Z1mQp
SMy9DtzL5pzRyQgw7kIeXLbi6jufUFd9pxN+
xnzKLf9mY5AcnGToTrbSL+jnMT67wG+c34+Q
PeVfucHNUePBxsbz2+4xbXiViSQyCQGv
) ; KSK; alg = RSASHA256; key id = 18772
1800 RRSIG DNSKEY 8 2 1800 (
20160428190224 20160329190224 14460 dnssex.nl.
cFSFtJE+DBGNxb52AweFaVHBe5Ue5MDpqNdC
TIneUnEhP2m+vK4zJ/TraK0WdQFpsX63pod8
PZ9y03vHUfewivyonCCBD3DcNdoU9subhN22
tez9Ct8Z5/9E4RAz7orXal4M1VUEhRcXSEH8
SJW20mfVsqJAiKqqNeGB/pAj23I= )
1800 RRSIG DNSKEY 8 2 1800 (
20160428190224 20160329190224 18772 dnssex.nl.
oiiwo/7NYacePqohEp50261elhm6Dieh4j2S
VZGAHU5gqLIQeW9CxKJKtSCkBVgUo4cvO4Rn
2tzArAuclDvBrMXRIoct8u7f96moeFE+x5FI
DYqICiV6k449ljj9o4t/5G7q2CRsEfxZKpTI
A/L0+uDk0RwVVzL45+TnilcsmZs= )
*.dnssex.nl. 1800 IN TXT "Doing It Safe Is Better"
1800 RRSIG TXT 8 2 1800 (
20160428190224 20160329190224 14460 dnssex.nl.
FUZSTyvZfeuuOpCmNzVKOfITRHJ6/ygjmnnb
XGBxVUyQjoLuYXwD5XqZWGw4iKH6QeSDfGCx
4MPqA4qQmW7Wwth7mat9yMfA4+p2sO84bysl
7/BG9+W2G+q1uQiM9bX9V42P2X/XuW5Y/t9Y
8u1sljQ7D8WwS6naH/vbaJxnDBw= )
14400 NSEC a.dnssex.nl. TXT RRSIG NSEC
14400 RRSIG NSEC 8 2 14400 (
20160428190224 20160329190224 14460 dnssex.nl.
os6INm6q2eXknD5z8TpfbK00uxVbQefMvHcR
/RNX/kh0xXvzAaaDOV+Ge/Ko+2dXnKP+J1LY
G9ffXNpdbaQy5ygzH5F041GJst4566GdG/jt
7Z7vLHYxEBTpZfxo+PLsXQXH3VTemZyuWyDf
qJzafXJVH1F0nDrcXmMlR6jlBHA= )
www.dnssex.nl. 1800 IN CNAME a.dnssex.nl.
1800 RRSIG CNAME 8 3 1800 (
20160428190224 20160329190224 14460 dnssex.nl.
Omv42q/uVvdNsWQoSrQ6m6w6U7r7Abga7uF4
25b3gZlse0C+WyMyGFMGUbapQm7azvBpreeo
uKJHjzd+ufoG+Oul6vU9vyoj+ejgHzGLGbJQ
HftfP+UqP5SWvAaipP/LULTWKPuiBcLDLiBI
PGTfsq0DB6R+qCDTV0fNnkgxEBQ= )
14400 NSEC dnssex.nl. CNAME RRSIG NSEC
14400 RRSIG NSEC 8 3 14400 (
20160428190224 20160329190224 14460 dnssex.nl.
TBN3ddfZW+kC84/g3QlNNJMeLZoyCalPQylt
KXXLPGuxfGpl3RYRY8KaHbP+5a8MnHjqjuMB
Lofb7yKMFxpSzMh8E36vnOqry1mvkSakNj9y
9jM8PwDjcpYUwn/ql76MsmNgEV5CLeQ7lyH4
AOrL79yOSQVI3JHJIjKSiz88iSw= )
a.dnssex.nl. 1800 IN A 139.162.196.78
1800 RRSIG A 8 3 1800 (
20160428190224 20160329190224 14460 dnssex.nl.
OXHpFj9nSpKi5yA/ULH7MOpGAWfyJ2yC/2xa
Pw0fqSY4QvcRt+V3adcFA4H9+P1b32GpxEjB
lXmCJID+H4lYkhUR4r4IOZBVtKG2SJEBZXip
pH00UkOIBiXxbGzfX8VL04v2G/YxUgLW57kA
aknaeTOkJsO20Y+8wmR9EtzaRFI= )
1800 AAAA 2a01:7e00::f03c:91ff:fef1:6735
1800 RRSIG AAAA 8 3 1800 (
20160428190224 20160329190224 14460 dnssex.nl.
jrepc/VnRzJypnrG0WDEqaAr3HMjWrPxJNX0
86gbFjZG07QxBmrA1rj0jM9YEWTjjyWb2tT7
lQhzKDYX/0XdOVUeeOM4FoSks80V+pWR8fvj
AZ5HmX69g36tLosMDKNR4lXcrpv89QovG4Hr
/r58fxEKEFJqrLDjMo6aOrg+uKA= )
14400 NSEC www.dnssex.nl. A AAAA RRSIG NSEC
14400 RRSIG NSEC 8 3 14400 (
20160428190224 20160329190224 14460 dnssex.nl.
S+UM62wXRNNFN3QDWK5YFWUbHBXC4aqaqinZ
A2ZDeC+IQgyw7vazPz7cLI5T0YXXks0HTMlr
soEjKnnRZsqSO9EuUavPNE1hh11Jjm0fB+5+
+Uro0EmA5Dhgc0Z2VpbXVQEhNDf/pI1gem15
RffN2tBYNykZn4Has2ySgRaaRYQ= )`

75
plugin/file/ds_test.go Normal file
View File

@@ -0,0 +1,75 @@
package file
import (
"strings"
"testing"
"github.com/coredns/coredns/plugin/pkg/dnsrecorder"
"github.com/coredns/coredns/plugin/test"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
var dsTestCases = []test.Case{
{
Qname: "a.delegated.miek.nl.", Qtype: dns.TypeDS,
Ns: []dns.RR{
test.NS("delegated.miek.nl. 1800 IN NS a.delegated.miek.nl."),
test.NS("delegated.miek.nl. 1800 IN NS ns-ext.nlnetlabs.nl."),
},
Extra: []dns.RR{
test.A("a.delegated.miek.nl. 1800 IN A 139.162.196.78"),
test.AAAA("a.delegated.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"),
},
},
{
Qname: "_udp.delegated.miek.nl.", Qtype: dns.TypeDS,
Ns: []dns.RR{
test.NS("delegated.miek.nl. 1800 IN NS a.delegated.miek.nl."),
test.NS("delegated.miek.nl. 1800 IN NS ns-ext.nlnetlabs.nl."),
},
Extra: []dns.RR{
test.A("a.delegated.miek.nl. 1800 IN A 139.162.196.78"),
test.AAAA("a.delegated.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"),
},
},
{
// This works *here* because we skip the server routing for DS in core/dnsserver/server.go
Qname: "_udp.miek.nl.", Qtype: dns.TypeDS,
Rcode: dns.RcodeNameError,
Ns: []dns.RR{
test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"),
},
},
{
Qname: "miek.nl.", Qtype: dns.TypeDS,
Ns: []dns.RR{
test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"),
},
},
}
func TestLookupDS(t *testing.T) {
zone, err := Parse(strings.NewReader(dbMiekNLDelegation), testzone, "stdin", 0)
if err != nil {
t.Fatalf("Expected no error when reading zone, got %q", err)
}
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: zone}, Names: []string{testzone}}}
ctx := context.TODO()
for _, tc := range dsTestCases {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := fm.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("Expected no error, got %v\n", err)
return
}
resp := rec.Msg
test.SortAndCheck(t, resp, tc)
}
}

159
plugin/file/ent_test.go Normal file
View File

@@ -0,0 +1,159 @@
package file
import (
"strings"
"testing"
"github.com/coredns/coredns/plugin/pkg/dnsrecorder"
"github.com/coredns/coredns/plugin/test"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
var entTestCases = []test.Case{
{
Qname: "b.c.miek.nl.", Qtype: dns.TypeA,
Ns: []dns.RR{
test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"),
},
},
{
Qname: "b.c.miek.nl.", Qtype: dns.TypeA, Do: true,
Ns: []dns.RR{
test.NSEC("a.miek.nl. 14400 IN NSEC a.b.c.miek.nl. A RRSIG NSEC"),
test.RRSIG("a.miek.nl. 14400 IN RRSIG NSEC 8 3 14400 20160502144311 20160402144311 12051 miek.nl. d5XZEy6SUpq98ZKUlzqhAfkLI9pQPc="),
test.RRSIG("miek.nl. 1800 IN RRSIG SOA 8 2 1800 20160502144311 20160402144311 12051 miek.nl. KegoBxA3Tbrhlc4cEdkRiteIkOfsq"),
test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"),
},
Extra: []dns.RR{test.OPT(4096, true)},
},
}
func TestLookupEnt(t *testing.T) {
zone, err := Parse(strings.NewReader(dbMiekENTNL), testzone, "stdin", 0)
if err != nil {
t.Fatalf("expect no error when reading zone, got %q", err)
}
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: zone}, Names: []string{testzone}}}
ctx := context.TODO()
for _, tc := range entTestCases {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := fm.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("expected no error, got %v\n", err)
return
}
resp := rec.Msg
test.SortAndCheck(t, resp, tc)
}
}
// fdjfdjkf
const dbMiekENTNL = `; File written on Sat Apr 2 16:43:11 2016
; dnssec_signzone version 9.10.3-P4-Ubuntu
miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. (
1282630057 ; serial
14400 ; refresh (4 hours)
3600 ; retry (1 hour)
604800 ; expire (1 week)
14400 ; minimum (4 hours)
)
1800 RRSIG SOA 8 2 1800 (
20160502144311 20160402144311 12051 miek.nl.
KegoBxA3Tbrhlc4cEdkRiteIkOfsqD4oCLLM
ISJ5bChWy00LGHUlAnHVu5Ti96hUjVNmGSxa
xtGSuAAMFCr52W8pAB8LBIlu9B6QZUPHMccr
SuzxAX3ioawk2uTjm+k8AGPT4RoQdXemGLAp
zJTASolTVmeMTh5J0sZTZJrtvZ0= )
1800 NS linode.atoom.net.
1800 RRSIG NS 8 2 1800 (
20160502144311 20160402144311 12051 miek.nl.
m0cOHL6Rre/0jZPXe+0IUjs/8AFASRCvDbSx
ZQsRDSlZgS6RoMP3OC77cnrKDVlfZ2Vhq3Ce
nYPoGe0/atB92XXsilmstx4HTSU64gsV9iLN
Xkzk36617t7zGOl/qumqfaUXeA9tihItzEim
6SGnufVZI4o8xeyaVCNDDuN0bvY= )
14400 NSEC a.miek.nl. NS SOA RRSIG NSEC DNSKEY
14400 RRSIG NSEC 8 2 14400 (
20160502144311 20160402144311 12051 miek.nl.
BCWVgwxWrs4tBjS9QXKkftCUbiLi40NyH1yA
nbFy1wCKQ2jDH00810+ia4b66QrjlAKgxE9z
9U7MKSMV86sNkyAtlCi+2OnjtWF6sxPdJO7k
CHeg46XBjrQuiJRY8CneQX56+IEPdufLeqPR
l+ocBQ2UkGhXmQdWp3CFDn2/eqU= )
1800 DNSKEY 256 3 8 (
AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6
E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5EC
IoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb
2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXH
Py7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz
) ; ZSK; alg = RSASHA256; key id = 12051
1800 DNSKEY 257 3 8 (
AwEAAcWdjBl4W4wh/hPxMDcBytmNCvEngIgB
9Ut3C2+QI0oVz78/WK9KPoQF7B74JQ/mjO4f
vIncBmPp6mFNxs9/WQX0IXf7oKviEVOXLjct
R4D1KQLX0wprvtUIsQFIGdXaO6suTT5eDbSd
6tTwu5xIkGkDmQhhH8OQydoEuCwV245ZwF/8
AIsqBYDNQtQ6zhd6jDC+uZJXg/9LuPOxFHbi
MTjp6j3CCW0kHbfM/YHZErWWtjPj3U3Z7knQ
SIm5PO5FRKBEYDdr5UxWJ/1/20SrzI3iztvP
wHDsA2rdHm/4YRzq7CvG4N0t9ac/T0a0Sxba
/BUX2UVPWaIVBdTRBtgHi0s=
) ; KSK; alg = RSASHA256; key id = 33694
1800 RRSIG DNSKEY 8 2 1800 (
20160502144311 20160402144311 12051 miek.nl.
YNpi1jRDQKpnsQEjIjxqy+kJGaYnV16e8Iug
40c82y4pee7kIojFUllSKP44qiJpCArxF557
tfjfwBd6c4hkqCScGPZXJ06LMyG4u//rhVMh
4hyKcxzQFKxmrFlj3oQGksCI8lxGX6RxiZuR
qv2ol2lUWrqetpAL+Zzwt71884E= )
1800 RRSIG DNSKEY 8 2 1800 (
20160502144311 20160402144311 33694 miek.nl.
jKpLDEeyadgM0wDgzEk6sBBdWr2/aCrkAOU/
w6dYIafN98f21oIYQfscV1gc7CTsA0vwzzUu
x0QgwxoNLMvSxxjOiW/2MzF8eozczImeCWbl
ad/pVCYH6Jn5UBrZ5RCWMVcs2RP5KDXWeXKs
jEN/0EmQg5qNd4zqtlPIQinA9I1HquJAnS56
pFvYyGIbZmGEbhR18sXVBeTWYr+zOMHn2quX
0kkrx2udz+sPg7i4yRsLdhw138gPRy1qvbaC
8ELs1xo1mC9pTlDOhz24Q3iXpVAU1lXLYOh9
nUP1/4UvZEYXHBUQk/XPRciojniWjAF825x3
QoSivMHblBwRdAKJSg== )
a.miek.nl. 1800 IN A 127.0.0.1
1800 RRSIG A 8 3 1800 (
20160502144311 20160402144311 12051 miek.nl.
lUOYdSxScjyYz+Ebc+nb6iTNgCohqj7K+Dat
97KE7haV2nP3LxdYuDCJYZpeyhsXDLHd4bFI
bInYPwJiC6DUCxPCuCWy0KYlZOWW8KCLX3Ia
BOPQbvIwLsJhnX+/tyMD9mXortoqATO79/6p
nNxvFeM8pFDwaih17fXMuFR/BsI= )
14400 NSEC a.b.c.miek.nl. A RRSIG NSEC
14400 RRSIG NSEC 8 3 14400 (
20160502144311 20160402144311 12051 miek.nl.
d5XZEy6SUp+TPRJQED+0R65zf2Yeo/1dlEA2
jYYvkXGSHXke4sg9nH8U3nr1rLcuqA1DsQgH
uMIjdENvXuZ+WCSwvIbhC+JEI6AyQ6Gfaf/D
I3mfu60C730IRByTrKM5C2rt11lwRQlbdaUY
h23/nn/q98ZKUlzqhAfkLI9pQPc= )
a.b.c.miek.nl. 1800 IN A 127.0.0.1
1800 RRSIG A 8 5 1800 (
20160502144311 20160402144311 12051 miek.nl.
FwgU5+fFD4hEebco3gvKQt3PXfY+dcOJr8dl
Ky4WLsONIdhP+4e9oprPisSLxImErY21BcrW
xzu1IZrYDsS8XBVV44lBx5WXEKvAOrUcut/S
OWhFZW7ncdIQCp32ZBIatiLRJEqXUjx+guHs
noFLiHix35wJWsRKwjGLIhH1fbs= )
14400 NSEC miek.nl. A RRSIG NSEC
14400 RRSIG NSEC 8 5 14400 (
20160502144311 20160402144311 12051 miek.nl.
lXgOqm9/jRRYvaG5jC1CDvTtGYxMroTzf4t4
jeYGb60+qI0q9sHQKfAJvoQ5o8o1qfR7OuiF
f544ipYT9eTcJRyGAOoJ37yMie7ZIoVJ91tB
r8YdzZ9Q6x3v1cbwTaQiacwhPZhGYOw63qIs
q5IQErIPos2sNk+y9D8BEce2DO4= )`

113
plugin/file/example_org.go Normal file
View File

@@ -0,0 +1,113 @@
package file
// exampleOrgSigned is a fake signed example.org zone with two delegations,
// one signed (with DSs) and one "normal".
const exampleOrgSigned = `
example.org. 1800 IN SOA a.iana-servers.net. devnull.example.org. (
1282630057 ; serial
14400 ; refresh (4 hours)
3600 ; retry (1 hour)
604800 ; expire (1 week)
14400 ; minimum (4 hours)
)
1800 RRSIG SOA 13 2 1800 (
20161129153240 20161030153240 49035 example.org.
GVnMpFmN+6PDdgCtlYDEYBsnBNDgYmEJNvos
Bk9+PNTPNWNst+BXCpDadTeqRwrr1RHEAQ7j
YWzNwqn81pN+IA== )
1800 NS a.iana-servers.net.
1800 NS b.iana-servers.net.
1800 RRSIG NS 13 2 1800 (
20161129153240 20161030153240 49035 example.org.
llrHoIuwjnbo28LOt4p5zWAs98XGqrXicKVI
Qxyaf/ORM8boJvW2XrKr3nj6Y8FKMhzd287D
5PBzVCL6MZyjQg== )
14400 NSEC a.example.org. NS SOA RRSIG NSEC DNSKEY
14400 RRSIG NSEC 13 2 14400 (
20161129153240 20161030153240 49035 example.org.
BQROf1swrmYi3GqpP5M/h5vTB8jmJ/RFnlaX
7fjxvV7aMvXCsr3ekWeB2S7L6wWFihDYcKJg
9BxVPqxzBKeaqg== )
1800 DNSKEY 256 3 13 (
UNTqlHbC51EbXuY0rshW19Iz8SkCuGVS+L0e
bQj53dvtNlaKfWmtTauC797FoyVLbQwoMy/P
G68SXgLCx8g+9g==
) ; ZSK; alg = ECDSAP256SHA256; key id = 49035
1800 RRSIG DNSKEY 13 2 1800 (
20161129153240 20161030153240 49035 example.org.
LnLHyqYJaCMOt7EHB4GZxzAzWLwEGCTFiEhC
jj1X1VuQSjJcN42Zd3yF+jihSW6huknrig0Z
Mqv0FM6mJ/qPKg== )
a.delegated.example.org. 1800 IN A 139.162.196.78
1800 TXT "obscured"
1800 AAAA 2a01:7e00::f03c:91ff:fef1:6735
archive.example.org. 1800 IN CNAME a.example.org.
1800 RRSIG CNAME 13 3 1800 (
20161129153240 20161030153240 49035 example.org.
SDFW1z/PN9knzH8BwBvmWK0qdIwMVtGrMgRw
7lgy4utRrdrRdCSLZy3xpkmkh1wehuGc4R0S
05Z3DPhB0Fg5BA== )
14400 NSEC delegated.example.org. CNAME RRSIG NSEC
14400 RRSIG NSEC 13 3 14400 (
20161129153240 20161030153240 49035 example.org.
DQqLSVNl8F6v1K09wRU6/M6hbHy2VUddnOwn
JusJjMlrAOmoOctCZ/N/BwqCXXBA+d9yFGdH
knYumXp+BVPBAQ== )
www.example.org. 1800 IN CNAME a.example.org.
1800 RRSIG CNAME 13 3 1800 (
20161129153240 20161030153240 49035 example.org.
adzujOxCV0uBV4OayPGfR11iWBLiiSAnZB1R
slmhBFaDKOKSNYijGtiVPeaF+EuZs63pzd4y
6Nm2Iq9cQhAwAA== )
14400 NSEC example.org. CNAME RRSIG NSEC
14400 RRSIG NSEC 13 3 14400 (
20161129153240 20161030153240 49035 example.org.
jy3f96GZGBaRuQQjuqsoP1YN8ObZF37o+WkV
PL7TruzI7iNl0AjrUDy9FplP8Mqk/HWyvlPe
N3cU+W8NYlfDDQ== )
a.example.org. 1800 IN A 139.162.196.78
1800 RRSIG A 13 3 1800 (
20161129153240 20161030153240 49035 example.org.
41jFz0Dr8tZBN4Kv25S5dD4vTmviFiLx7xSA
qMIuLFm0qibKL07perKpxqgLqM0H1wreT4xz
I9Y4Dgp1nsOuMA== )
1800 AAAA 2a01:7e00::f03c:91ff:fef1:6735
1800 RRSIG AAAA 13 3 1800 (
20161129153240 20161030153240 49035 example.org.
brHizDxYCxCHrSKIu+J+XQbodRcb7KNRdN4q
VOWw8wHqeBsFNRzvFF6jwPQYphGP7kZh1KAb
VuY5ZVVhM2kHjw== )
14400 NSEC archive.example.org. A AAAA RRSIG NSEC
14400 RRSIG NSEC 13 3 14400 (
20161129153240 20161030153240 49035 example.org.
zIenVlg5ScLr157EWigrTGUgrv7W/1s49Fic
i2k+OVjZfT50zw+q5X6DPKkzfAiUhIuqs53r
hZUzZwV/1Wew9Q== )
delegated.example.org. 1800 IN NS a.delegated.example.org.
1800 IN NS ns-ext.nlnetlabs.nl.
1800 DS 10056 5 1 (
EE72CABD1927759CDDA92A10DBF431504B9E
1F13 )
1800 DS 10056 5 2 (
E4B05F87725FA86D9A64F1E53C3D0E625094
6599DFE639C45955B0ED416CDDFA )
1800 RRSIG DS 13 3 1800 (
20161129153240 20161030153240 49035 example.org.
rlNNzcUmtbjLSl02ZzQGUbWX75yCUx0Mug1j
HtKVqRq1hpPE2S3863tIWSlz+W9wz4o19OI4
jbznKKqk+DGKog== )
14400 NSEC sub.example.org. NS DS RRSIG NSEC
14400 RRSIG NSEC 13 3 14400 (
20161129153240 20161030153240 49035 example.org.
lNQ5kRTB26yvZU5bFn84LYFCjwWTmBcRCDbD
cqWZvCSw4LFOcqbz1/wJKIRjIXIqnWIrfIHe
fZ9QD5xZsrPgUQ== )
sub.example.org. 1800 IN NS sub1.example.net.
1800 IN NS sub2.example.net.
14400 NSEC www.example.org. NS RRSIG NSEC
14400 RRSIG NSEC 13 3 14400 (
20161129153240 20161030153240 49035 example.org.
VYjahdV+TTkA3RBdnUI0hwXDm6U5k/weeZZr
ix1znORpOELbeLBMJW56cnaG+LGwOQfw9qqj
bOuULDst84s4+g== )
`

138
plugin/file/file.go Normal file
View File

@@ -0,0 +1,138 @@
// Package file implements a file backend.
package file
import (
"fmt"
"io"
"log"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
type (
// File is the plugin that reads zone data from disk.
File struct {
Next plugin.Handler
Zones Zones
}
// Zones maps zone names to a *Zone.
Zones struct {
Z map[string]*Zone // A map mapping zone (origin) to the Zone's data
Names []string // All the keys from the map Z as a string slice.
}
)
// ServeDNS implements the plugin.Handle interface.
func (f File) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
state := request.Request{W: w, Req: r}
qname := state.Name()
// TODO(miek): match the qname better in the map
zone := plugin.Zones(f.Zones.Names).Matches(qname)
if zone == "" {
return plugin.NextOrFailure(f.Name(), f.Next, ctx, w, r)
}
z, ok := f.Zones.Z[zone]
if !ok || z == nil {
return dns.RcodeServerFailure, nil
}
// This is only for when we are a secondary zones.
if r.Opcode == dns.OpcodeNotify {
if z.isNotify(state) {
m := new(dns.Msg)
m.SetReply(r)
m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true
state.SizeAndDo(m)
w.WriteMsg(m)
log.Printf("[INFO] Notify from %s for %s: checking transfer", state.IP(), zone)
ok, err := z.shouldTransfer()
if ok {
z.TransferIn()
} else {
log.Printf("[INFO] Notify from %s for %s: no serial increase seen", state.IP(), zone)
}
if err != nil {
log.Printf("[WARNING] Notify from %s for %s: failed primary check: %s", state.IP(), zone, err)
}
return dns.RcodeSuccess, nil
}
log.Printf("[INFO] Dropping notify from %s for %s", state.IP(), zone)
return dns.RcodeSuccess, nil
}
if z.Expired != nil && *z.Expired {
log.Printf("[ERROR] Zone %s is expired", zone)
return dns.RcodeServerFailure, nil
}
if state.QType() == dns.TypeAXFR || state.QType() == dns.TypeIXFR {
xfr := Xfr{z}
return xfr.ServeDNS(ctx, w, r)
}
answer, ns, extra, result := z.Lookup(state, qname)
m := new(dns.Msg)
m.SetReply(r)
m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true
m.Answer, m.Ns, m.Extra = answer, ns, extra
switch result {
case Success:
case NoData:
case NameError:
m.Rcode = dns.RcodeNameError
case Delegation:
m.Authoritative = false
case ServerFailure:
return dns.RcodeServerFailure, nil
}
state.SizeAndDo(m)
m, _ = state.Scrub(m)
w.WriteMsg(m)
return dns.RcodeSuccess, nil
}
// Name implements the Handler interface.
func (f File) Name() string { return "file" }
// Parse parses the zone in filename and returns a new Zone or an error.
// If serial >= 0 it will reload the zone, if the SOA hasn't changed
// it returns an error indicating nothing was read.
func Parse(f io.Reader, origin, fileName string, serial int64) (*Zone, error) {
tokens := dns.ParseZone(f, dns.Fqdn(origin), fileName)
z := NewZone(origin, fileName)
seenSOA := false
for x := range tokens {
if x.Error != nil {
return nil, x.Error
}
if !seenSOA && serial >= 0 {
if s, ok := x.RR.(*dns.SOA); ok {
if s.Serial == uint32(serial) { // same zone
return nil, fmt.Errorf("no change in serial: %d", serial)
}
seenSOA = true
}
}
if err := z.Insert(x.RR); err != nil {
return nil, err
}
}
if !seenSOA {
return nil, fmt.Errorf("file %q has no SOA record", fileName)
}
return z, nil
}

31
plugin/file/file_test.go Normal file
View File

@@ -0,0 +1,31 @@
package file
import (
"strings"
"testing"
)
func BenchmarkFileParseInsert(b *testing.B) {
for i := 0; i < b.N; i++ {
Parse(strings.NewReader(dbMiekENTNL), testzone, "stdin", 0)
}
}
func TestParseNoSOA(t *testing.T) {
_, err := Parse(strings.NewReader(dbNoSOA), "example.org.", "stdin", 0)
if err == nil {
t.Fatalf("zone %q should have failed to load", "example.org.")
}
if !strings.Contains(err.Error(), "no SOA record") {
t.Fatalf("zone %q should have failed to load with no soa error: %s", "example.org.", err)
}
}
const dbNoSOA = `
$TTL 1M
$ORIGIN example.org.
www IN A 192.168.0.14
mail IN A 192.168.0.15
imap IN CNAME mail
`

253
plugin/file/glue_test.go Normal file
View File

@@ -0,0 +1,253 @@
package file
import (
"strings"
"testing"
"github.com/coredns/coredns/plugin/pkg/dnsrecorder"
"github.com/coredns/coredns/plugin/test"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
// another personal zone (helps in testing as my secondary is NSD
// atoom = atom in English.
var atoomTestCases = []test.Case{
{
Qname: atoom, Qtype: dns.TypeNS, Do: true,
Answer: []dns.RR{
test.NS("atoom.net. 1800 IN NS linode.atoom.net."),
test.NS("atoom.net. 1800 IN NS ns-ext.nlnetlabs.nl."),
test.NS("atoom.net. 1800 IN NS omval.tednet.nl."),
test.RRSIG("atoom.net. 1800 IN RRSIG NS 8 2 1800 20170112031301 20161213031301 53289 atoom.net. DLe+G1 jlw="),
},
Extra: []dns.RR{
test.OPT(4096, true),
test.A("linode.atoom.net. 1800 IN A 176.58.119.54"),
test.AAAA("linode.atoom.net. 1800 IN AAAA 2a01:7e00::f03c:91ff:fe79:234c"),
test.RRSIG("linode.atoom.net. 1800 IN RRSIG A 8 3 1800 20170112031301 20161213031301 53289 atoom.net. Z4Ka4OLDoyxj72CL vkI="),
test.RRSIG("linode.atoom.net. 1800 IN RRSIG AAAA 8 3 1800 20170112031301 20161213031301 53289 atoom.net. l+9Qc914zFH/okG2fzJ1q olQ="),
},
},
}
func TestLookupGlue(t *testing.T) {
zone, err := Parse(strings.NewReader(dbAtoomNetSigned), atoom, "stdin", 0)
if err != nil {
t.Fatalf("Expected no error when reading zone, got %q", err)
}
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{atoom: zone}, Names: []string{atoom}}}
ctx := context.TODO()
for _, tc := range atoomTestCases {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := fm.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("Expected no error, got %v\n", err)
return
}
resp := rec.Msg
test.SortAndCheck(t, resp, tc)
}
}
const dbAtoomNetSigned = `
; File written on Tue Dec 13 04:13:01 2016
; dnssec_signzone version 9.10.3-P4-Debian
atoom.net. 1800 IN SOA linode.atoom.net. miek.miek.nl. (
1481602381 ; serial
14400 ; refresh (4 hours)
3600 ; retry (1 hour)
604800 ; expire (1 week)
14400 ; minimum (4 hours)
)
1800 RRSIG SOA 8 2 1800 (
20170112031301 20161213031301 53289 atoom.net.
GZ30uFuGATKzwHXgpEwK70qjdXSAqmbB5d4z
e7WTibvJDPLa1ptZBI7Zuod2KMOkT1ocSvhL
U7makhdv0BQx+5RSaP25mAmPIzfU7/T7R+DJ
5q1GLlDSvOprfyMUlwOgZKZinesSdUa9gRmu
8E+XnPNJ/jcTrGzzaDjn1/irrM0= )
1800 NS omval.tednet.nl.
1800 NS linode.atoom.net.
1800 NS ns-ext.nlnetlabs.nl.
1800 RRSIG NS 8 2 1800 (
20170112031301 20161213031301 53289 atoom.net.
D8Sd9JpXIOxOrUF5Hi1ASutyQwP7JNu8XZxA
rse86A6L01O8H8sCNib2VEoJjHuZ/dDEogng
OgmfqeFy04cpSX19GAk3bkx8Lr6aEat3nqIC
XA/xsCCfXy0NKZpI05zntHPbbP5tF/NvpE7n
0+oLtlHSPEg1ZnEgwNoLe+G1jlw= )
1800 A 176.58.119.54
1800 RRSIG A 8 2 1800 (
20170112031301 20161213031301 53289 atoom.net.
mrjiUFNCqDgCW8TuhjzcMh0V841uC224QvwH
0+OvYhcve9twbX3Y12PSFmz77Xz3Jg9WAj4I
qhh3iHUac4dzUXyC702DT62yMF/9CMUO0+Ee
b6wRtvPHr2Tt0i/xV/BTbArInIvurXJrvKvo
LsZHOfsg7dZs6Mvdpe/CgwRExpk= )
1800 AAAA 2a01:7e00::f03c:91ff:fe79:234c
1800 RRSIG AAAA 8 2 1800 (
20170112031301 20161213031301 53289 atoom.net.
EkMxX2vUaP4h0qbWlHaT4yNhm8MrPMZTn/3R
zNw+i3oF2cLMWKh6GCfuIX/x5ID706o8kfum
bxTYwuTe1LJ+GoZHWEiH8VCa1laTlh8l3qSi
PZKU8339rr5cCYluk6p9PbAuRkYYOEruNg42
wPOx46dsAlvp2XpOaOeJtU64QGQ= )
14400 NSEC deb.atoom.net. A NS SOA AAAA RRSIG NSEC DNSKEY
14400 RRSIG NSEC 8 2 14400 (
20170112031301 20161213031301 53289 atoom.net.
P7Stx7lqRKl8tbTAAaJ0W6UhgJwZz3cjpM8z
eplbhXEVohKtyJ9xgptKt1vreH6lkhzciar5
EB9Nj0VOmcthiht/+As8aEKmf8UlcJ2EbLII
NT7NUaasxsrLE2rjjX5mEtzOZ1uQAGiU8Hnk
XdGweTgIVFuiCcMCgaKpC2TRrMw= )
1800 DNSKEY 256 3 8 (
AwEAAeDZTH9YT9qLMPlq4VrxX7H3GbWcqCrC
tXc9RT/hf96GN+ttnnEQVaJY8Gbly3IZpYQW
MwaCi0t30UULXE3s9FUQtl4AMbplyiz9EF8L
/XoBS1yhGm5WV5u608ihoPaRkYNyVV3egb5Y
hA5EXWy2vfsa1XWPpxvSAhlqM0YENtP3
) ; ZSK; alg = RSASHA256; key id = 53289
1800 DNSKEY 257 3 8 (
AwEAAepN7Vo8enDCruVduVlGxTDIv7QG0wJQ
fTL1hMy4k0Yf/7dXzrn5bZT4ytBvH1hoBImH
mtTrQo6DQlBBVXDJXTyQjQozaHpN1HhTJJTz
IXl8UrdbkLWvz6QSeJPmBBYQRAqylUA2KE29
nxyiNboheDLiIWyQ7Q/Op7lYaKMdb555kQAs
b/XT4Tb3/3BhAjcofNofNBjDjPq2i8pAo8HU
5mW5/Pl+ZT/S0aqQPnCkHk/iofSRu3ZdBzkH
54eoC+BdyXb7gTbPGRr+1gMbf/rzhRiZ4vnX
NoEzGAXmorKzJHANNb6KQ/932V9UDHm9wbln
6y3s7IBvsMX5KF8vo81Stkc=
) ; KSK; alg = RSASHA256; key id = 19114
1800 RRSIG DNSKEY 8 2 1800 (
20170112031301 20161213031301 19114 atoom.net.
IEjViubKdef8RWB5bcnirqVcqDk16irkywJZ
sBjMyNs03/a+sl0UHEGAB7qCC+Rn+RDaM5It
WF+Gha6BwRIN9NuSg3BwB2h1nJtHw61pMVU9
2j9Q3pq7X1xoTBAcwY95t5a1xlw0iTCaLu1L
Iu/PbVp1gj1o8BF/PiYilvZJGUjaTgsi+YNi
2kiWpp6afO78/W4nfVx+lQBmpyfX1lwL5PEC
9f5PMbzRmOapvUBc2XdddGywLdmlNsLHimGV
t7kkHZHOWQR1TvvMbU3dsC0bFCrBVGDhEuxC
hATR+X5YV0AyDSyrew7fOGJKrapwMWS3yRLr
FAt0Vcxno5lwQImbCQ== )
1800 RRSIG DNSKEY 8 2 1800 (
20170112031301 20161213031301 53289 atoom.net.
sSxdgPT+gFZPN0ot6lZRGqOwvONUEsg0uEbf
kh19JlWHu/qvq5HOOK2VOW/UnswpVmtpFk0W
z/jiCNHifjpCCVn5tfCMZDLGekmPOjdobw24
swBuGjnn0NHvxHoN6S+mb+AR6V/dLjquNUda
yzBc2Ua+XtQ7SCLKIvEhcNg9H3o= )
deb.atoom.net. 1800 IN A 176.58.119.54
1800 RRSIG A 8 3 1800 (
20170112031301 20161213031301 53289 atoom.net.
ZW7jm/VDa/I9DxWlE7Cm+HHymiVv4Wk5UGYI
Uf/g0EfxLCBR6SwL5QKuV1z7xoWKaiNqqrmc
gg35xgskKyS8QHgCCODhDzcIKe+MSsBXbY04
AtrC5dV3JJQoA65Ng/48hwcyghAjXKrA2Yyq
GXf2DSvWeIV9Jmk0CsOELP24dpk= )
1800 TXT "v=spf1 a ip6:2a01:7e00::f03c:91ff:fe79:234c ~all"
1800 RRSIG TXT 8 3 1800 (
20170112031301 20161213031301 53289 atoom.net.
fpvVJ+Z6tzSd9yETn/PhLSCRISwRD1c3ET80
8twnx3XfAPQfV2R8dw7pz8Vw4TSxvf19bAZc
PWRjW682gb7gAxoJshCXBYabMfqExrBc9V1S
ezwm3D93xNMyegxzHx2b/H8qp3ZWdsMLTvvN
Azu7P4iyO+WRWT0R7bJGrdTwRz8= )
1800 AAAA 2a01:7e00::f03c:91ff:fe79:234c
1800 RRSIG AAAA 8 3 1800 (
20170112031301 20161213031301 53289 atoom.net.
aaPF6NqXfWamzi+xUDVeYa7StJUVM1tDsL34
w5uozFRZ0f4K/Z88Kk5CgztxmtpNNKGdLWa0
iryUJsbVWAbSQfrZNkNckBtczMNxGgjqn97A
2//F6ajH/qrR3dWcCm+VJMgu3UPqAxLiCaYO
GQUx6Y8JA1VIM/RJAM6BhgNxjD0= )
14400 NSEC lafhart.atoom.net. A TXT AAAA RRSIG NSEC
14400 RRSIG NSEC 8 3 14400 (
20170112031301 20161213031301 53289 atoom.net.
1Llad64NDWcz8CyBu2TsyANrJ9Tpfm5257sY
FPYF579p3c9Imwp9kYEO1zMEKgNoXBN/sQnd
YCugq3r2GAI6bfJj8sV5bt6GKuZcGHMESug4
uh2gU0NDcCA4GPdBYGdusePwV0RNpcRnVCFA
fsACp+22j3uwRUbCh0re0ufbAs4= )
lafhart.atoom.net. 1800 IN A 178.79.160.171
1800 RRSIG A 8 3 1800 (
20170112031301 20161213031301 53289 atoom.net.
fruP6cvMVICXEV8NcheS73NWLCEKlO1FgW6B
35D2GhtfYZe+M23V5YBRtlVCCrAdS0etdCOf
xH9yt3u2kVvDXuMRiQr1zJPRDEq3cScYumpd
bOO8cjHiCic5lEcRVWNNHXyGtpqTvrp9CxOu
IQw1WgAlZyKj43zGg3WZi6OTKLg= )
14400 NSEC linode.atoom.net. A RRSIG NSEC
14400 RRSIG NSEC 8 3 14400 (
20170112031301 20161213031301 53289 atoom.net.
2AUWXbScL0jIJ7G6UsJAlUs+bgSprZ1zY6v/
iVB5BAYwZD6pPky7LZdzvPEHh0aNLGIFbbU8
SDJI7u/e4RUTlE+8yyjl6obZNfNKyJFqE5xN
1BJ8sjFrVn6KaHIDKEOZunNb1MlMfCRkLg9O
94zg04XEgVUfaYCPxvLs3fCEgzw= )
voordeur.atoom.net. 1800 IN A 77.249.87.46
1800 RRSIG A 8 3 1800 (
20170112031301 20161213031301 53289 atoom.net.
SzJz0NaKLRA/lW4CxgMHgeuQLp5QqFEjQv3I
zfPtY4joQsZn8RN8RLECcpcPKjbC8Dj6mxIJ
dd2vwhsCVlZKMNcZUOfpB7eGx1TR9HnzMkY9
OdTt30a9+tktagrJEoy31vAhj1hJqLbSgvOa
pRr1P4ZpQ53/qH8JX/LOmqfWTdg= )
14400 NSEC www.atoom.net. A RRSIG NSEC
14400 RRSIG NSEC 8 3 14400 (
20170112031301 20161213031301 53289 atoom.net.
CETJhUJy1rKjVj9wsW1549gth+/Z37//BI6S
nxJ+2Oq63jEjlbznmyo5hvFW54DbVUod+cLo
N9PdlNQDr1XsRBgWhkKW37RkuoRVEPwqRykv
xzn9i7CgYKAAHFyWMGihBLkV9ByPp8GDR8Zr
DEkrG3ErDlBcwi3FqGZFsSOW2xg= )
www.atoom.net. 1800 IN CNAME deb.atoom.net.
1800 RRSIG CNAME 8 3 1800 (
20170112031301 20161213031301 53289 atoom.net.
1lhG6iTtbeesBCVOrA8a7+V2gogCuXzKgSi8
6K0Pzq2CwqTScdNcZvcDOIbLq45Am5p09PIj
lXnd2fw6WAxphwvRhmwCve3uTZMUt5STw7oi
0rED7GMuFUSC/BX0XVly7NET3ECa1vaK6RhO
hDSsKPWFI7to4d1z6tQ9j9Kvm4Y= )
14400 NSEC atoom.net. CNAME RRSIG NSEC
14400 RRSIG NSEC 8 3 14400 (
20170112031301 20161213031301 53289 atoom.net.
CC4yCYP1q75/gTmPz+mVM6Lam2foPP5oTccY
RtROuTkgbt8DtAoPe304vmNazWBlGidnWJeD
YyAAe3znIHP0CgrxjD/hRL9FUzMnVrvB3mnx
4W13wP1rE97RqJxV1kk22Wl3uCkVGy7LCjb0
JLFvzCe2fuMe7YcTzI+t1rioTP0= )
linode.atoom.net. 1800 IN A 176.58.119.54
1800 RRSIG A 8 3 1800 (
20170112031301 20161213031301 53289 atoom.net.
Z4Ka4OLDha4eQNWs3GtUd1Cumr48RUnH523I
nZzGXtpQNou70qsm5Jt8n/HmsZ4L5DoxomRz
rgZTGnrqj43+A16UUGfVEk6SfUUHOgxgspQW
zoaqk5/5mQO1ROsLKY8RqaRqzvbToHvqeZEh
VkTPVA02JK9UFlKqoyxj72CLvkI= )
1800 AAAA 2a01:7e00::f03c:91ff:fe79:234c
1800 RRSIG AAAA 8 3 1800 (
20170112031301 20161213031301 53289 atoom.net.
l+9Qce/EQyKrTJVKLv7iatjuCO285ckd5Oie
P2LzWVsL4tW04oHzieKZwIuNBRE+px8g5qrT
LIK2TikCGL1xHAd7CT7gbCtDcZ7jHmSTmMTJ
405nOV3G3xWelreLI5Fn5ck8noEsF64kiw1y
XfkyQn2B914zFH/okG2fzJ1qolQ= )
14400 NSEC voordeur.atoom.net. A AAAA RRSIG NSEC
14400 RRSIG NSEC 8 3 14400 (
20170112031301 20161213031301 53289 atoom.net.
Owzmz7QrVL2Gw2njEsUVEknMl2amx1HG9X3K
tO+Ihyy4tApiUFxUjAu3P/30QdqbB85h7s//
ipwX/AmQJNoxTScR3nHt9qDqJ044DPmiuh0l
NuIjguyZRANApmKCTA6AoxXIUqToIIjfVzi/
PxXE6T3YIPlK7Bxgv1lcCBJ1fmE= )`
const atoom = "atoom.net."

View File

@@ -0,0 +1,32 @@
package file
import (
"strings"
"testing"
"github.com/coredns/coredns/plugin/test"
)
// Make sure the external miekg/dns dependency is up to date
func TestInclude(t *testing.T) {
name, rm, err := test.TempFile(".", "foo\tIN\tA\t127.0.0.1\n")
if err != nil {
t.Fatalf("Unable to create tmpfile %q: %s", name, err)
}
defer rm()
zone := `$ORIGIN example.org.
@ IN SOA sns.dns.icann.org. noc.dns.icann.org. 2017042766 7200 3600 1209600 3600
$INCLUDE ` + name + "\n"
z, err := Parse(strings.NewReader(zone), "example.org.", "test", 0)
if err != nil {
t.Errorf("Unable to parse zone %q: %s", "example.org.", err)
}
if _, ok := z.Search("foo.example.org."); !ok {
t.Errorf("Failed to find %q in parsed zone", "foo.example.org.")
}
}

467
plugin/file/lookup.go Normal file
View File

@@ -0,0 +1,467 @@
package file
import (
"github.com/coredns/coredns/plugin/file/tree"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
// Result is the result of a Lookup
type Result int
const (
// Success is a successful lookup.
Success Result = iota
// NameError indicates a nameerror
NameError
// Delegation indicates the lookup resulted in a delegation.
Delegation
// NoData indicates the lookup resulted in a NODATA.
NoData
// ServerFailure indicates a server failure during the lookup.
ServerFailure
)
// Lookup looks up qname and qtype in the zone. When do is true DNSSEC records are included.
// Three sets of records are returned, one for the answer, one for authority and one for the additional section.
func (z *Zone) Lookup(state request.Request, qname string) ([]dns.RR, []dns.RR, []dns.RR, Result) {
qtype := state.QType()
do := state.Do()
if !z.NoReload {
z.reloadMu.RLock()
}
defer func() {
if !z.NoReload {
z.reloadMu.RUnlock()
}
}()
// If z is a secondary zone we might not have transferred it, meaning we have
// all zone context setup, except the actual record. This means (for one thing) the apex
// is empty and we don't have a SOA record.
soa := z.Apex.SOA
if soa == nil {
return nil, nil, nil, ServerFailure
}
if qtype == dns.TypeSOA {
return z.soa(do), z.ns(do), nil, Success
}
if qtype == dns.TypeNS && qname == z.origin {
nsrrs := z.ns(do)
glue := z.Glue(nsrrs, do)
return nsrrs, nil, glue, Success
}
var (
found, shot bool
parts string
i int
elem, wildElem *tree.Elem
)
// Lookup:
// * Per label from the right, look if it exists. We do this to find potential
// delegation records.
// * If the per-label search finds nothing, we will look for the wildcard at the
// level. If found we keep it around. If we don't find the complete name we will
// use the wildcard.
//
// Main for-loop handles delegation and finding or not finding the qname.
// If found we check if it is a CNAME/DNAME and do CNAME processing
// We also check if we have type and do a nodata resposne.
//
// If not found, we check the potential wildcard, and use that for further processing.
// If not found and no wildcard we will process this as an NXDOMAIN response.
for {
parts, shot = z.nameFromRight(qname, i)
// We overshot the name, break and check if we previously found something.
if shot {
break
}
elem, found = z.Tree.Search(parts)
if !found {
// Apex will always be found, when we are here we can search for a wildcard
// and save the result of that search. So when nothing match, but we have a
// wildcard we should expand the wildcard.
wildcard := replaceWithAsteriskLabel(parts)
if wild, found := z.Tree.Search(wildcard); found {
wildElem = wild
}
// Keep on searching, because maybe we hit an empty-non-terminal (which aren't
// stored in the tree. Only when we have match the full qname (and possible wildcard
// we can be confident that we didn't find anything.
i++
continue
}
// If we see DNAME records, we should return those.
if dnamerrs := elem.Types(dns.TypeDNAME); dnamerrs != nil {
// Only one DNAME is allowed per name. We just pick the first one to synthesize from.
dname := dnamerrs[0]
if cname := synthesizeCNAME(state.Name(), dname.(*dns.DNAME)); cname != nil {
answer, ns, extra, rcode := z.searchCNAME(state, elem, []dns.RR{cname})
if do {
sigs := elem.Types(dns.TypeRRSIG)
sigs = signatureForSubType(sigs, dns.TypeDNAME)
dnamerrs = append(dnamerrs, sigs...)
}
// The relevant DNAME RR should be included in the answer section,
// if the DNAME is being employed as a substitution instruction.
answer = append(dnamerrs, answer...)
return answer, ns, extra, rcode
}
// The domain name that owns a DNAME record is allowed to have other RR types
// at that domain name, except those have restrictions on what they can coexist
// with (e.g. another DNAME). So there is nothing special left here.
}
// If we see NS records, it means the name as been delegated, and we should return the delegation.
if nsrrs := elem.Types(dns.TypeNS); nsrrs != nil {
glue := z.Glue(nsrrs, do)
// If qtype == NS, we should returns success to put RRs in answer.
if qtype == dns.TypeNS {
return nsrrs, nil, glue, Success
}
if do {
dss := z.typeFromElem(elem, dns.TypeDS, do)
nsrrs = append(nsrrs, dss...)
}
return nil, nsrrs, glue, Delegation
}
i++
}
// What does found and !shot mean - do we ever hit it?
if found && !shot {
return nil, nil, nil, ServerFailure
}
// Found entire name.
if found && shot {
if rrs := elem.Types(dns.TypeCNAME); len(rrs) > 0 && qtype != dns.TypeCNAME {
return z.searchCNAME(state, elem, rrs)
}
rrs := elem.Types(qtype, qname)
// NODATA
if len(rrs) == 0 {
ret := z.soa(do)
if do {
nsec := z.typeFromElem(elem, dns.TypeNSEC, do)
ret = append(ret, nsec...)
}
return nil, ret, nil, NoData
}
// Additional section processing for MX, SRV. Check response and see if any of the names are in baliwick -
// if so add IP addresses to the additional section.
additional := additionalProcessing(z, rrs, do)
if do {
sigs := elem.Types(dns.TypeRRSIG)
sigs = signatureForSubType(sigs, qtype)
rrs = append(rrs, sigs...)
}
return rrs, z.ns(do), additional, Success
}
// Haven't found the original name.
// Found wildcard.
if wildElem != nil {
auth := z.ns(do)
if rrs := wildElem.Types(dns.TypeCNAME, qname); len(rrs) > 0 {
return z.searchCNAME(state, wildElem, rrs)
}
rrs := wildElem.Types(qtype, qname)
// NODATA response.
if len(rrs) == 0 {
ret := z.soa(do)
if do {
nsec := z.typeFromElem(wildElem, dns.TypeNSEC, do)
ret = append(ret, nsec...)
}
return nil, ret, nil, Success
}
if do {
// An NSEC is needed to say no longer name exists under this wildcard.
if deny, found := z.Tree.Prev(qname); found {
nsec := z.typeFromElem(deny, dns.TypeNSEC, do)
auth = append(auth, nsec...)
}
sigs := wildElem.Types(dns.TypeRRSIG, qname)
sigs = signatureForSubType(sigs, qtype)
rrs = append(rrs, sigs...)
}
return rrs, auth, nil, Success
}
rcode := NameError
// Hacky way to get around empty-non-terminals. If a longer name does exist, but this qname, does not, it
// must be an empty-non-terminal. If so, we do the proper NXDOMAIN handling, but set the rcode to be success.
if x, found := z.Tree.Next(qname); found {
if dns.IsSubDomain(qname, x.Name()) {
rcode = Success
}
}
ret := z.soa(do)
if do {
deny, _ := z.Tree.Prev(qname) // TODO(miek): *found* was not used here.
nsec := z.typeFromElem(deny, dns.TypeNSEC, do)
ret = append(ret, nsec...)
if rcode != NameError {
goto Out
}
ce, found := z.ClosestEncloser(qname)
// wildcard denial only for NXDOMAIN
if found {
// wildcard denial
wildcard := "*." + ce.Name()
if ss, found := z.Tree.Prev(wildcard); found {
// Only add this nsec if it is different than the one already added
if ss.Name() != deny.Name() {
nsec := z.typeFromElem(ss, dns.TypeNSEC, do)
ret = append(ret, nsec...)
}
}
}
}
Out:
return nil, ret, nil, rcode
}
// Return type tp from e and add signatures (if they exists) and do is true.
func (z *Zone) typeFromElem(elem *tree.Elem, tp uint16, do bool) []dns.RR {
rrs := elem.Types(tp)
if do {
sigs := elem.Types(dns.TypeRRSIG)
sigs = signatureForSubType(sigs, tp)
if len(sigs) > 0 {
rrs = append(rrs, sigs...)
}
}
return rrs
}
func (z *Zone) soa(do bool) []dns.RR {
if do {
ret := append([]dns.RR{z.Apex.SOA}, z.Apex.SIGSOA...)
return ret
}
return []dns.RR{z.Apex.SOA}
}
func (z *Zone) ns(do bool) []dns.RR {
if do {
ret := append(z.Apex.NS, z.Apex.SIGNS...)
return ret
}
return z.Apex.NS
}
// TODO(miek): should be better named, like aditionalProcessing?
func (z *Zone) searchCNAME(state request.Request, elem *tree.Elem, rrs []dns.RR) ([]dns.RR, []dns.RR, []dns.RR, Result) {
qtype := state.QType()
do := state.Do()
if do {
sigs := elem.Types(dns.TypeRRSIG)
sigs = signatureForSubType(sigs, dns.TypeCNAME)
if len(sigs) > 0 {
rrs = append(rrs, sigs...)
}
}
targetName := rrs[0].(*dns.CNAME).Target
elem, _ = z.Tree.Search(targetName)
if elem == nil {
if !dns.IsSubDomain(z.origin, targetName) {
rrs = append(rrs, z.externalLookup(state, targetName, qtype)...)
}
return rrs, z.ns(do), nil, Success
}
i := 0
Redo:
cname := elem.Types(dns.TypeCNAME)
if len(cname) > 0 {
rrs = append(rrs, cname...)
if do {
sigs := elem.Types(dns.TypeRRSIG)
sigs = signatureForSubType(sigs, dns.TypeCNAME)
if len(sigs) > 0 {
rrs = append(rrs, sigs...)
}
}
targetName := cname[0].(*dns.CNAME).Target
elem, _ = z.Tree.Search(targetName)
if elem == nil {
if !dns.IsSubDomain(z.origin, targetName) {
if !dns.IsSubDomain(z.origin, targetName) {
rrs = append(rrs, z.externalLookup(state, targetName, qtype)...)
}
}
return rrs, z.ns(do), nil, Success
}
i++
if i > maxChain {
return rrs, z.ns(do), nil, Success
}
goto Redo
}
targets := cnameForType(elem.All(), qtype)
if len(targets) > 0 {
rrs = append(rrs, targets...)
if do {
sigs := elem.Types(dns.TypeRRSIG)
sigs = signatureForSubType(sigs, qtype)
if len(sigs) > 0 {
rrs = append(rrs, sigs...)
}
}
}
return rrs, z.ns(do), nil, Success
}
func cnameForType(targets []dns.RR, origQtype uint16) []dns.RR {
ret := []dns.RR{}
for _, target := range targets {
if target.Header().Rrtype == origQtype {
ret = append(ret, target)
}
}
return ret
}
func (z *Zone) externalLookup(state request.Request, target string, qtype uint16) []dns.RR {
m, e := z.Proxy.Lookup(state, target, qtype)
if e != nil {
// TODO(miek): debugMsg for this as well? Log?
return nil
}
return m.Answer
}
// signatureForSubType range through the signature and return the correct ones for the subtype.
func signatureForSubType(rrs []dns.RR, subtype uint16) []dns.RR {
sigs := []dns.RR{}
for _, sig := range rrs {
if s, ok := sig.(*dns.RRSIG); ok {
if s.TypeCovered == subtype {
sigs = append(sigs, s)
}
}
}
return sigs
}
// Glue returns any potential glue records for nsrrs.
func (z *Zone) Glue(nsrrs []dns.RR, do bool) []dns.RR {
glue := []dns.RR{}
for _, rr := range nsrrs {
if ns, ok := rr.(*dns.NS); ok && dns.IsSubDomain(ns.Header().Name, ns.Ns) {
glue = append(glue, z.searchGlue(ns.Ns, do)...)
}
}
return glue
}
// searchGlue looks up A and AAAA for name.
func (z *Zone) searchGlue(name string, do bool) []dns.RR {
glue := []dns.RR{}
// A
if elem, found := z.Tree.Search(name); found {
glue = append(glue, elem.Types(dns.TypeA)...)
if do {
sigs := elem.Types(dns.TypeRRSIG)
sigs = signatureForSubType(sigs, dns.TypeA)
glue = append(glue, sigs...)
}
}
// AAAA
if elem, found := z.Tree.Search(name); found {
glue = append(glue, elem.Types(dns.TypeAAAA)...)
if do {
sigs := elem.Types(dns.TypeRRSIG)
sigs = signatureForSubType(sigs, dns.TypeAAAA)
glue = append(glue, sigs...)
}
}
return glue
}
// additionalProcessing checks the current answer section and retrieves A or AAAA records
// (and possible SIGs) to need to be put in the additional section.
func additionalProcessing(z *Zone, answer []dns.RR, do bool) (extra []dns.RR) {
for _, rr := range answer {
name := ""
switch x := rr.(type) {
case *dns.SRV:
name = x.Target
case *dns.MX:
name = x.Mx
}
if !dns.IsSubDomain(z.origin, name) {
continue
}
elem, _ := z.Tree.Search(name)
if elem == nil {
continue
}
sigs := elem.Types(dns.TypeRRSIG)
for _, addr := range []uint16{dns.TypeA, dns.TypeAAAA} {
if a := elem.Types(addr); a != nil {
extra = append(extra, a...)
if do {
sig := signatureForSubType(sigs, addr)
extra = append(extra, sig...)
}
}
}
}
return extra
}
const maxChain = 8

194
plugin/file/lookup_test.go Normal file
View File

@@ -0,0 +1,194 @@
package file
import (
"strings"
"testing"
"github.com/coredns/coredns/plugin/pkg/dnsrecorder"
"github.com/coredns/coredns/plugin/test"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
var dnsTestCases = []test.Case{
{
Qname: "www.miek.nl.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.A("a.miek.nl. 1800 IN A 139.162.196.78"),
test.CNAME("www.miek.nl. 1800 IN CNAME a.miek.nl."),
},
Ns: miekAuth,
},
{
Qname: "www.miek.nl.", Qtype: dns.TypeAAAA,
Answer: []dns.RR{
test.AAAA("a.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"),
test.CNAME("www.miek.nl. 1800 IN CNAME a.miek.nl."),
},
Ns: miekAuth,
},
{
Qname: "miek.nl.", Qtype: dns.TypeSOA,
Answer: []dns.RR{
test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"),
},
Ns: miekAuth,
},
{
Qname: "miek.nl.", Qtype: dns.TypeAAAA,
Answer: []dns.RR{
test.AAAA("miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"),
},
Ns: miekAuth,
},
{
Qname: "mIeK.NL.", Qtype: dns.TypeAAAA,
Answer: []dns.RR{
test.AAAA("miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"),
},
Ns: miekAuth,
},
{
Qname: "miek.nl.", Qtype: dns.TypeMX,
Answer: []dns.RR{
test.MX("miek.nl. 1800 IN MX 1 aspmx.l.google.com."),
test.MX("miek.nl. 1800 IN MX 10 aspmx2.googlemail.com."),
test.MX("miek.nl. 1800 IN MX 10 aspmx3.googlemail.com."),
test.MX("miek.nl. 1800 IN MX 5 alt1.aspmx.l.google.com."),
test.MX("miek.nl. 1800 IN MX 5 alt2.aspmx.l.google.com."),
},
Ns: miekAuth,
},
{
Qname: "a.miek.nl.", Qtype: dns.TypeSRV,
Ns: []dns.RR{
test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"),
},
},
{
Qname: "b.miek.nl.", Qtype: dns.TypeA,
Rcode: dns.RcodeNameError,
Ns: []dns.RR{
test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"),
},
},
{
Qname: "srv.miek.nl.", Qtype: dns.TypeSRV,
Answer: []dns.RR{
test.SRV("srv.miek.nl. 1800 IN SRV 10 10 8080 a.miek.nl."),
},
Extra: []dns.RR{
test.A("a.miek.nl. 1800 IN A 139.162.196.78"),
test.AAAA("a.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"),
},
Ns: miekAuth,
},
{
Qname: "mx.miek.nl.", Qtype: dns.TypeMX,
Answer: []dns.RR{
test.MX("mx.miek.nl. 1800 IN MX 10 a.miek.nl."),
},
Extra: []dns.RR{
test.A("a.miek.nl. 1800 IN A 139.162.196.78"),
test.AAAA("a.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"),
},
Ns: miekAuth,
},
}
const (
testzone = "miek.nl."
testzone1 = "dnssex.nl."
)
func TestLookup(t *testing.T) {
zone, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin", 0)
if err != nil {
t.Fatalf("expect no error when reading zone, got %q", err)
}
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: zone}, Names: []string{testzone}}}
ctx := context.TODO()
for _, tc := range dnsTestCases {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := fm.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("expected no error, got %v\n", err)
return
}
resp := rec.Msg
test.SortAndCheck(t, resp, tc)
}
}
func TestLookupNil(t *testing.T) {
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: nil}, Names: []string{testzone}}}
ctx := context.TODO()
m := dnsTestCases[0].Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
fm.ServeDNS(ctx, rec, m)
}
func BenchmarkFileLookup(b *testing.B) {
zone, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin", 0)
if err != nil {
return
}
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: zone}, Names: []string{testzone}}}
ctx := context.TODO()
rec := dnsrecorder.New(&test.ResponseWriter{})
tc := test.Case{
Qname: "www.miek.nl.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.CNAME("www.miek.nl. 1800 IN CNAME a.miek.nl."),
test.A("a.miek.nl. 1800 IN A 139.162.196.78"),
},
}
m := tc.Msg()
b.ResetTimer()
for i := 0; i < b.N; i++ {
fm.ServeDNS(ctx, rec, m)
}
}
const dbMiekNL = `
$TTL 30M
$ORIGIN miek.nl.
@ IN SOA linode.atoom.net. miek.miek.nl. (
1282630057 ; Serial
4H ; Refresh
1H ; Retry
7D ; Expire
4H ) ; Negative Cache TTL
IN NS linode.atoom.net.
IN NS ns-ext.nlnetlabs.nl.
IN NS omval.tednet.nl.
IN NS ext.ns.whyscream.net.
IN MX 1 aspmx.l.google.com.
IN MX 5 alt1.aspmx.l.google.com.
IN MX 5 alt2.aspmx.l.google.com.
IN MX 10 aspmx2.googlemail.com.
IN MX 10 aspmx3.googlemail.com.
IN A 139.162.196.78
IN AAAA 2a01:7e00::f03c:91ff:fef1:6735
a IN A 139.162.196.78
IN AAAA 2a01:7e00::f03c:91ff:fef1:6735
www IN CNAME a
archive IN CNAME a
srv IN SRV 10 10 8080 a.miek.nl.
mx IN MX 10 a.miek.nl.`

82
plugin/file/notify.go Normal file
View File

@@ -0,0 +1,82 @@
package file
import (
"fmt"
"log"
"net"
"github.com/coredns/coredns/plugin/pkg/rcode"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
// isNotify checks if state is a notify message and if so, will *also* check if it
// is from one of the configured masters. If not it will not be a valid notify
// message. If the zone z is not a secondary zone the message will also be ignored.
func (z *Zone) isNotify(state request.Request) bool {
if state.Req.Opcode != dns.OpcodeNotify {
return false
}
if len(z.TransferFrom) == 0 {
return false
}
// 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
}
}
return false
}
// Notify will send notifies to all configured TransferTo IP addresses.
func (z *Zone) Notify() {
go notify(z.origin, z.TransferTo)
}
// notify sends notifies to the configured remote servers. It will try up to three times
// before giving up on a specific remote. We will sequentially loop through "to"
// until they all have replied (or have 3 failed attempts).
func notify(zone string, to []string) error {
m := new(dns.Msg)
m.SetNotify(zone)
c := new(dns.Client)
for _, t := range to {
if t == "*" {
continue
}
if err := notifyAddr(c, m, t); err != nil {
log.Printf("[ERROR] " + err.Error())
} else {
log.Printf("[INFO] Sent notify for zone %q to %q", zone, t)
}
}
return nil
}
func notifyAddr(c *dns.Client, m *dns.Msg, s string) error {
var err error
code := dns.RcodeServerFailure
for i := 0; i < 3; i++ {
ret, _, err := c.Exchange(m, s)
if err != nil {
continue
}
code = ret.Rcode
if code == dns.RcodeSuccess {
return nil
}
}
if err != nil {
return fmt.Errorf("notify for zone %q was not accepted by %q: %q", m.Question[0].Name, s, err)
}
return fmt.Errorf("notify for zone %q was not accepted by %q: rcode was %q", m.Question[0].Name, s, rcode.ToString(code))
}

28
plugin/file/nsec3_test.go Normal file
View File

@@ -0,0 +1,28 @@
package file
import (
"strings"
"testing"
)
func TestParseNSEC3PARAM(t *testing.T) {
_, err := Parse(strings.NewReader(nsec3paramTest), "miek.nl", "stdin", 0)
if err == nil {
t.Fatalf("expected error when reading zone, got nothing")
}
}
func TestParseNSEC3(t *testing.T) {
_, err := Parse(strings.NewReader(nsec3Test), "miek.nl", "stdin", 0)
if err == nil {
t.Fatalf("expected error when reading zone, got nothing")
}
}
const nsec3paramTest = `miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1460175181 14400 3600 604800 14400
miek.nl. 1800 IN NS omval.tednet.nl.
miek.nl. 0 IN NSEC3PARAM 1 0 5 A3DEBC9CC4F695C7`
const nsec3Test = `example.org. 1800 IN SOA sns.dns.icann.org. noc.dns.icann.org. 2016082508 7200 3600 1209600 3600
aub8v9ce95ie18spjubsr058h41n7pa5.example.org. 284 IN NSEC3 1 1 5 D0CBEAAF0AC77314 AUB95P93VPKP55G6U5S4SGS7LS61ND85 NS SOA TXT RRSIG DNSKEY NSEC3PARAM
aub8v9ce95ie18spjubsr058h41n7pa5.example.org. 284 IN RRSIG NSEC3 8 2 600 20160910232502 20160827231002 14028 example.org. XBNpA7KAIjorPbXvTinOHrc1f630aHic2U716GHLHA4QMx9cl9ss4QjR Wj2UpDM9zBW/jNYb1xb0yjQoez/Jv200w0taSWjRci5aUnRpOi9bmcrz STHb6wIUjUsbJ+NstQsUwVkj6679UviF1FqNwr4GlJnWG3ZrhYhE+NI6 s0k=`

72
plugin/file/reload.go Normal file
View File

@@ -0,0 +1,72 @@
package file
import (
"log"
"os"
"path"
"github.com/fsnotify/fsnotify"
)
// Reload reloads a zone when it is changed on disk. If z.NoRoload is true, no reloading will be done.
func (z *Zone) Reload() error {
if z.NoReload {
return nil
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
err = watcher.Add(path.Dir(z.file))
if err != nil {
return err
}
go func() {
// TODO(miek): needs to be killed on reload.
for {
select {
case event := <-watcher.Events:
if path.Clean(event.Name) == z.file {
reader, err := os.Open(z.file)
if err != nil {
log.Printf("[ERROR] Failed to open `%s' for `%s': %v", z.file, z.origin, err)
continue
}
serial := z.SOASerialIfDefined()
zone, err := Parse(reader, z.origin, z.file, serial)
if err != nil {
log.Printf("[WARNING] Parsing zone `%s': %v", z.origin, err)
continue
}
// copy elements we need
z.reloadMu.Lock()
z.Apex = zone.Apex
z.Tree = zone.Tree
z.reloadMu.Unlock()
log.Printf("[INFO] Successfully reloaded zone `%s'", z.origin)
z.Notify()
}
case <-z.ReloadShutdown:
watcher.Close()
return
}
}
}()
return nil
}
// SOASerialIfDefined returns the SOA's serial if the zone has a SOA record in the Apex, or
// -1 otherwise.
func (z *Zone) SOASerialIfDefined() int64 {
z.reloadMu.Lock()
defer z.reloadMu.Unlock()
if z.Apex.SOA != nil {
return int64(z.Apex.SOA.Serial)
}
return -1
}

View File

@@ -0,0 +1,82 @@
package file
import (
"io/ioutil"
"log"
"os"
"strings"
"testing"
"time"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
func TestZoneReload(t *testing.T) {
log.SetOutput(ioutil.Discard)
fileName, rm, err := test.TempFile(".", reloadZoneTest)
if err != nil {
t.Fatalf("failed to create zone: %s", err)
}
defer rm()
reader, err := os.Open(fileName)
if err != nil {
t.Fatalf("failed to open zone: %s", err)
}
z, err := Parse(reader, "miek.nl", fileName, 0)
if err != nil {
t.Fatalf("failed to parse zone: %s", err)
}
z.Reload()
r := new(dns.Msg)
r.SetQuestion("miek.nl", dns.TypeSOA)
state := request.Request{W: &test.ResponseWriter{}, Req: r}
if _, _, _, res := z.Lookup(state, "miek.nl."); res != Success {
t.Fatalf("failed to lookup, got %d", res)
}
r = new(dns.Msg)
r.SetQuestion("miek.nl", dns.TypeNS)
state = request.Request{W: &test.ResponseWriter{}, Req: r}
if _, _, _, res := z.Lookup(state, "miek.nl."); res != Success {
t.Fatalf("failed to lookup, got %d", res)
}
if len(z.All()) != 5 {
t.Fatalf("expected 5 RRs, got %d", len(z.All()))
}
if err := ioutil.WriteFile(fileName, []byte(reloadZone2Test), 0644); err != nil {
t.Fatalf("failed to write new zone data: %s", err)
}
// Could still be racy, but we need to wait a bit for the event to be seen
time.Sleep(1 * time.Second)
if len(z.All()) != 3 {
t.Fatalf("expected 3 RRs, got %d", len(z.All()))
}
}
func TestZoneReloadSOAChange(t *testing.T) {
_, err := Parse(strings.NewReader(reloadZoneTest), "miek.nl.", "stdin", 1460175181)
if err == nil {
t.Fatalf("zone should not have been re-parsed")
}
}
const reloadZoneTest = `miek.nl. 1627 IN SOA linode.atoom.net. miek.miek.nl. 1460175181 14400 3600 604800 14400
miek.nl. 1627 IN NS ext.ns.whyscream.net.
miek.nl. 1627 IN NS omval.tednet.nl.
miek.nl. 1627 IN NS linode.atoom.net.
miek.nl. 1627 IN NS ns-ext.nlnetlabs.nl.
`
const reloadZone2Test = `miek.nl. 1627 IN SOA linode.atoom.net. miek.miek.nl. 1460175182 14400 3600 604800 14400
miek.nl. 1627 IN NS ext.ns.whyscream.net.
miek.nl. 1627 IN NS omval.tednet.nl.
`

199
plugin/file/secondary.go Normal file
View File

@@ -0,0 +1,199 @@
package file
import (
"log"
"math/rand"
"time"
"github.com/miekg/dns"
)
// TransferIn retrieves the zone from the masters, parses it and sets it live.
func (z *Zone) TransferIn() error {
if len(z.TransferFrom) == 0 {
return nil
}
m := new(dns.Msg)
m.SetAxfr(z.origin)
z1 := z.Copy()
var (
Err error
tr string
)
Transfer:
for _, tr = range z.TransferFrom {
t := new(dns.Transfer)
c, err := t.In(m, tr)
if err != nil {
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 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' from: %q: %v", z.origin, tr, err)
Err = err
continue Transfer
}
}
}
Err = nil
break
}
if Err != nil {
return Err
}
z.Tree = z1.Tree
z.Apex = z1.Apex
*z.Expired = false
log.Printf("[INFO] Transferred: %s from %s", z.origin, tr)
return nil
}
// shouldTransfer checks the primaries of zone, retrieves the SOA record, checks the current serial
// and the remote serial and will return true if the remote one is higher than the locally configured one.
func (z *Zone) shouldTransfer() (bool, error) {
c := new(dns.Client)
c.Net = "tcp" // do this query over TCP to minimize spoofing
m := new(dns.Msg)
m.SetQuestion(z.origin, dns.TypeSOA)
var Err error
serial := -1
Transfer:
for _, tr := range z.TransferFrom {
Err = nil
ret, _, err := c.Exchange(m, tr)
if err != nil || ret.Rcode != dns.RcodeSuccess {
Err = err
continue
}
for _, a := range ret.Answer {
if a.Header().Rrtype == dns.TypeSOA {
serial = int(a.(*dns.SOA).Serial)
break Transfer
}
}
}
if serial == -1 {
return false, Err
}
if z.Apex.SOA == nil {
return true, Err
}
return less(z.Apex.SOA.Serial, uint32(serial)), Err
}
// less return true of a is smaller than b when taking RFC 1982 serial arithmetic into account.
func less(a, b uint32) bool {
if a < b {
return (b - a) <= MaxSerialIncrement
}
return (a - b) > MaxSerialIncrement
}
// Update updates the secondary zone according to its SOA. It will run for the life time of the server
// and uses the SOA parameters. Every refresh it will check for a new SOA number. If that fails (for all
// server) it wil retry every retry interval. If the zone failed to transfer before the expire, the zone
// will be marked expired.
func (z *Zone) Update() error {
// If we don't have a SOA, we don't have a zone, wait for it to appear.
for z.Apex.SOA == nil {
time.Sleep(1 * time.Second)
}
retryActive := false
Restart:
refresh := time.Second * time.Duration(z.Apex.SOA.Refresh)
retry := time.Second * time.Duration(z.Apex.SOA.Retry)
expire := time.Second * time.Duration(z.Apex.SOA.Expire)
if refresh < time.Hour {
refresh = time.Hour
}
if retry < time.Hour {
retry = time.Hour
}
if refresh > 24*time.Hour {
refresh = 24 * time.Hour
}
if retry > 12*time.Hour {
retry = 12 * time.Hour
}
refreshTicker := time.NewTicker(refresh)
retryTicker := time.NewTicker(retry)
expireTicker := time.NewTicker(expire)
for {
select {
case <-expireTicker.C:
if !retryActive {
break
}
*z.Expired = true
case <-retryTicker.C:
if !retryActive {
break
}
time.Sleep(jitter(2000)) // 2s randomize
ok, err := z.shouldTransfer()
if err != nil && ok {
if err := z.TransferIn(); err != nil {
// transfer failed, leave retryActive true
break
}
retryActive = false
// transfer OK, possible new SOA, stop timers and redo
refreshTicker.Stop()
retryTicker.Stop()
expireTicker.Stop()
goto Restart
}
case <-refreshTicker.C:
time.Sleep(jitter(5000)) // 5s randomize
ok, err := z.shouldTransfer()
retryActive = err != nil
if err != nil && ok {
if err := z.TransferIn(); err != nil {
// transfer failed
retryActive = true
break
}
retryActive = false
// transfer OK, possible new SOA, stop timers and redo
refreshTicker.Stop()
retryTicker.Stop()
expireTicker.Stop()
goto Restart
}
}
}
}
// jitter returns a random duration between [0,n) * time.Millisecond
func jitter(n int) time.Duration {
r := rand.Intn(n)
return time.Duration(r) * time.Millisecond
}
// MaxSerialIncrement is the maximum difference between two serial numbers. If the difference between
// two serials is greater than this number, the smaller one is considered greater.
const MaxSerialIncrement uint32 = 2147483647

View File

@@ -0,0 +1,168 @@
package file
import (
"fmt"
"io/ioutil"
"log"
"testing"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
// TODO(miek): should test notifies as well, ie start test server (a real coredns one)...
// setup other test server that sends notify, see if CoreDNS comes calling for a zone
// tranfer
func TestLess(t *testing.T) {
const (
min = 0
max = 4294967295
low = 12345
high = 4000000000
)
if less(min, max) {
t.Fatalf("less: should be false")
}
if !less(max, min) {
t.Fatalf("less: should be true")
}
if !less(high, low) {
t.Fatalf("less: should be true")
}
if !less(7, 9) {
t.Fatalf("less; should be true")
}
}
type soa struct {
serial uint32
}
func (s *soa) Handler(w dns.ResponseWriter, req *dns.Msg) {
m := new(dns.Msg)
m.SetReply(req)
switch req.Question[0].Qtype {
case dns.TypeSOA:
m.Answer = make([]dns.RR, 1)
m.Answer[0] = test.SOA(fmt.Sprintf("%s IN SOA bla. bla. %d 0 0 0 0 ", testZone, s.serial))
w.WriteMsg(m)
case dns.TypeAXFR:
m.Answer = make([]dns.RR, 4)
m.Answer[0] = test.SOA(fmt.Sprintf("%s IN SOA bla. bla. %d 0 0 0 0 ", testZone, s.serial))
m.Answer[1] = test.A(fmt.Sprintf("%s IN A 127.0.0.1", testZone))
m.Answer[2] = test.A(fmt.Sprintf("%s IN A 127.0.0.1", testZone))
m.Answer[3] = test.SOA(fmt.Sprintf("%s IN SOA bla. bla. %d 0 0 0 0 ", testZone, s.serial))
w.WriteMsg(m)
}
}
func (s *soa) TransferHandler(w dns.ResponseWriter, req *dns.Msg) {
m := new(dns.Msg)
m.SetReply(req)
m.Answer = make([]dns.RR, 1)
m.Answer[0] = test.SOA(fmt.Sprintf("%s IN SOA bla. bla. %d 0 0 0 0 ", testZone, s.serial))
w.WriteMsg(m)
}
const testZone = "secondary.miek.nl."
func TestShouldTransfer(t *testing.T) {
soa := soa{250}
log.SetOutput(ioutil.Discard)
dns.HandleFunc(testZone, soa.Handler)
defer dns.HandleRemove(testZone)
s, addrstr, err := test.TCPServer("127.0.0.1:0")
if err != nil {
t.Fatalf("unable to run test server: %v", err)
}
defer s.Shutdown()
z := new(Zone)
z.origin = testZone
z.TransferFrom = []string{addrstr}
// when we have a nil SOA (initial state)
should, err := z.shouldTransfer()
if err != nil {
t.Fatalf("unable to run shouldTransfer: %v", err)
}
if !should {
t.Fatalf("shouldTransfer should return true for serial: %d", soa.serial)
}
// Serial smaller
z.Apex.SOA = test.SOA(fmt.Sprintf("%s IN SOA bla. bla. %d 0 0 0 0 ", testZone, soa.serial-1))
should, err = z.shouldTransfer()
if err != nil {
t.Fatalf("unable to run shouldTransfer: %v", err)
}
if !should {
t.Fatalf("shouldTransfer should return true for serial: %q", soa.serial-1)
}
// Serial equal
z.Apex.SOA = test.SOA(fmt.Sprintf("%s IN SOA bla. bla. %d 0 0 0 0 ", testZone, soa.serial))
should, err = z.shouldTransfer()
if err != nil {
t.Fatalf("unable to run shouldTransfer: %v", err)
}
if should {
t.Fatalf("shouldTransfer should return false for serial: %d", soa.serial)
}
}
func TestTransferIn(t *testing.T) {
soa := soa{250}
log.SetOutput(ioutil.Discard)
dns.HandleFunc(testZone, soa.Handler)
defer dns.HandleRemove(testZone)
s, addrstr, err := test.TCPServer("127.0.0.1:0")
if err != nil {
t.Fatalf("unable to run test server: %v", err)
}
defer s.Shutdown()
z := new(Zone)
z.Expired = new(bool)
z.origin = testZone
z.TransferFrom = []string{addrstr}
err = z.TransferIn()
if err != nil {
t.Fatalf("unable to run TransferIn: %v", err)
}
if z.Apex.SOA.String() != fmt.Sprintf("%s 3600 IN SOA bla. bla. 250 0 0 0 0", testZone) {
t.Fatalf("unknown SOA transferred")
}
}
func TestIsNotify(t *testing.T) {
z := new(Zone)
z.Expired = new(bool)
z.origin = testZone
state := newRequest(testZone, dns.TypeSOA)
// need to set opcode
state.Req.Opcode = dns.OpcodeNotify
z.TransferFrom = []string{"10.240.0.1:53"} // IP from from testing/responseWriter
if !z.isNotify(state) {
t.Fatal("should have been valid notify")
}
z.TransferFrom = []string{"10.240.0.2:53"}
if z.isNotify(state) {
t.Fatal("should have been invalid notify")
}
}
func newRequest(zone string, qtype uint16) request.Request {
m := new(dns.Msg)
m.SetQuestion("example.com.", dns.TypeA)
m.SetEdns0(4097, true)
return request.Request{W: &test.ResponseWriter{}, Req: m}
}

171
plugin/file/setup.go Normal file
View File

@@ -0,0 +1,171 @@
package file
import (
"fmt"
"os"
"path"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/dnsutil"
"github.com/coredns/coredns/plugin/proxy"
"github.com/mholt/caddy"
)
func init() {
caddy.RegisterPlugin("file", caddy.Plugin{
ServerType: "dns",
Action: setup,
})
}
func setup(c *caddy.Controller) error {
zones, err := fileParse(c)
if err != nil {
return plugin.Error("file", err)
}
// Add startup functions to notify the master(s).
for _, n := range zones.Names {
z := zones.Z[n]
c.OnStartup(func() error {
z.StartupOnce.Do(func() {
if len(z.TransferTo) > 0 {
z.Notify()
}
z.Reload()
})
return nil
})
}
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
return File{Next: next, Zones: zones}
})
return nil
}
func fileParse(c *caddy.Controller) (Zones, error) {
z := make(map[string]*Zone)
names := []string{}
origins := []string{}
config := dnsserver.GetConfig(c)
for c.Next() {
// file db.file [zones...]
if !c.NextArg() {
return Zones{}, c.ArgErr()
}
fileName := c.Val()
origins = make([]string, len(c.ServerBlockKeys))
copy(origins, c.ServerBlockKeys)
args := c.RemainingArgs()
if len(args) > 0 {
origins = args
}
if !path.IsAbs(fileName) && config.Root != "" {
fileName = path.Join(config.Root, fileName)
}
reader, err := os.Open(fileName)
if err != nil {
// bail out
return Zones{}, err
}
for i := range origins {
origins[i] = plugin.Host(origins[i]).Normalize()
zone, err := Parse(reader, origins[i], fileName, 0)
if err == nil {
z[origins[i]] = zone
} else {
return Zones{}, err
}
names = append(names, origins[i])
}
noReload := false
prxy := proxy.Proxy{}
t := []string{}
var e error
for c.NextBlock() {
switch c.Val() {
case "transfer":
t, _, e = TransferParse(c, false)
if e != nil {
return Zones{}, e
}
case "no_reload":
noReload = true
case "upstream":
args := c.RemainingArgs()
if len(args) == 0 {
return Zones{}, c.ArgErr()
}
ups, err := dnsutil.ParseHostPortOrFile(args...)
if err != nil {
return Zones{}, err
}
prxy = proxy.NewLookup(ups)
default:
return Zones{}, c.Errf("unknown property '%s'", c.Val())
}
for _, origin := range origins {
if t != nil {
z[origin].TransferTo = append(z[origin].TransferTo, t...)
}
z[origin].NoReload = noReload
z[origin].Proxy = prxy
}
}
}
return Zones{Z: z, Names: names}, nil
}
// TransferParse parses transfer statements: 'transfer to [address...]'.
func TransferParse(c *caddy.Controller, secondary bool) (tos, froms []string, err error) {
if !c.NextArg() {
return nil, nil, c.ArgErr()
}
value := c.Val()
switch value {
case "to":
tos = c.RemainingArgs()
for i := range tos {
if tos[i] != "*" {
normalized, err := dnsutil.ParseHostPort(tos[i], "53")
if err != nil {
return nil, nil, err
}
tos[i] = normalized
}
}
case "from":
if !secondary {
return nil, nil, fmt.Errorf("can't use `transfer from` when not being a secondary")
}
froms = c.RemainingArgs()
for i := range froms {
if froms[i] != "*" {
normalized, err := dnsutil.ParseHostPort(froms[i], "53")
if err != nil {
return nil, nil, err
}
froms[i] = normalized
} else {
return nil, nil, fmt.Errorf("can't use '*' in transfer from")
}
}
}
return
}

77
plugin/file/setup_test.go Normal file
View File

@@ -0,0 +1,77 @@
package file
import (
"testing"
"github.com/coredns/coredns/plugin/test"
"github.com/mholt/caddy"
)
func TestFileParse(t *testing.T) {
zoneFileName1, rm, err := test.TempFile(".", dbMiekNL)
if err != nil {
t.Fatal(err)
}
defer rm()
zoneFileName2, rm, err := test.TempFile(".", dbDnssexNLSigned)
if err != nil {
t.Fatal(err)
}
defer rm()
tests := []struct {
inputFileRules string
shouldErr bool
expectedZones Zones
}{
{
`file ` + zoneFileName1 + ` miek.nl {
transfer from 127.0.0.1
}`,
true,
Zones{},
},
{
`file`,
true,
Zones{},
},
{
`file ` + zoneFileName1 + ` miek.nl.`,
false,
Zones{Names: []string{"miek.nl."}},
},
{
`file ` + zoneFileName2 + ` dnssex.nl.`,
false,
Zones{Names: []string{"dnssex.nl."}},
},
{
`file ` + zoneFileName2 + ` 10.0.0.0/8`,
false,
Zones{Names: []string{"10.in-addr.arpa."}},
},
}
for i, test := range tests {
c := caddy.NewTestController("dns", test.inputFileRules)
actualZones, err := fileParse(c)
if err == nil && test.shouldErr {
t.Fatalf("Test %d expected errors, but got no error", i)
} else if err != nil && !test.shouldErr {
t.Fatalf("Test %d expected no errors, but got '%v'", i, err)
} else {
if len(actualZones.Names) != len(test.expectedZones.Names) {
t.Fatalf("Test %d expected %v, got %v", i, test.expectedZones.Names, actualZones.Names)
}
for j, name := range test.expectedZones.Names {
if actualZones.Names[j] != name {
t.Fatalf("Test %d expected %v for %d th zone, got %v", i, name, j, actualZones.Names[j])
}
}
}
}
}

48
plugin/file/tree/all.go Normal file
View File

@@ -0,0 +1,48 @@
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
}
// Do performs fn on all values stored in the tree. A boolean is returned indicating whether the
// Do traversal was interrupted by an Operation returning true. If fn alters stored values' sort
// relationships, future tree operation behaviors are undefined.
func (t *Tree) Do(fn func(e *Elem) bool) bool {
if t.Root == nil {
return false
}
return t.Root.do(fn)
}
func (n *Node) do(fn func(e *Elem) bool) (done bool) {
if n.Left != nil {
done = n.Left.do(fn)
if done {
return
}
}
done = fn(n.Elem)
if done {
return
}
if n.Right != nil {
done = n.Right.do(fn)
}
return
}

136
plugin/file/tree/elem.go Normal file
View File

@@ -0,0 +1,136 @@
package tree
import "github.com/miekg/dns"
// Elem is an element in the tree.
type Elem struct {
m map[uint16][]dns.RR
name string // owner name
}
// newElem returns a new elem.
func newElem(rr dns.RR) *Elem {
e := Elem{m: make(map[uint16][]dns.RR)}
e.m[rr.Header().Rrtype] = []dns.RR{rr}
return &e
}
// Types returns the RRs with type qtype from e. If qname is given (only the
// first one is used), the RR are copied and the owner is replaced with qname[0].
func (e *Elem) Types(qtype uint16, qname ...string) []dns.RR {
rrs := e.m[qtype]
if rrs != nil && len(qname) > 0 {
copied := make([]dns.RR, len(rrs))
for i := range rrs {
copied[i] = dns.Copy(rrs[i])
copied[i].Header().Name = qname[0]
}
return copied
}
return rrs
}
// All returns all RRs from e, regardless of type.
func (e *Elem) All() []dns.RR {
list := []dns.RR{}
for _, rrs := range e.m {
list = append(list, rrs...)
}
return list
}
// Name returns the name for this node.
func (e *Elem) Name() string {
if e.name != "" {
return e.name
}
for _, rrs := range e.m {
e.name = rrs[0].Header().Name
return e.name
}
return ""
}
// Empty returns true is e does not contain any RRs, i.e. is an
// empty-non-terminal.
func (e *Elem) Empty() bool {
return len(e.m) == 0
}
// Insert inserts rr into e. If rr is equal to existing rrs this is a noop.
func (e *Elem) Insert(rr dns.RR) {
t := rr.Header().Rrtype
if e.m == nil {
e.m = make(map[uint16][]dns.RR)
e.m[t] = []dns.RR{rr}
return
}
rrs, ok := e.m[t]
if !ok {
e.m[t] = []dns.RR{rr}
return
}
for _, er := range rrs {
if equalRdata(er, rr) {
return
}
}
rrs = append(rrs, rr)
e.m[t] = rrs
}
// Delete removes rr from e. When e is empty after the removal the returned bool is true.
func (e *Elem) Delete(rr dns.RR) (empty bool) {
if e.m == nil {
return true
}
t := rr.Header().Rrtype
rrs, ok := e.m[t]
if !ok {
return
}
for i, er := range rrs {
if equalRdata(er, rr) {
rrs = removeFromSlice(rrs, i)
e.m[t] = rrs
empty = len(rrs) == 0
if empty {
delete(e.m, t)
}
return
}
}
return
}
// Less is a tree helper function that calls less.
func Less(a *Elem, name string) int { return less(name, a.Name()) }
// Assuming the same type and name this will check if the rdata is equal as well.
func equalRdata(a, b dns.RR) bool {
switch x := a.(type) {
// TODO(miek): more types, i.e. all types. + tests for this.
case *dns.A:
return x.A.Equal(b.(*dns.A).A)
case *dns.AAAA:
return x.AAAA.Equal(b.(*dns.AAAA).AAAA)
case *dns.MX:
if x.Mx == b.(*dns.MX).Mx && x.Preference == b.(*dns.MX).Preference {
return true
}
}
return false
}
// removeFromSlice removes index i from the slice.
func removeFromSlice(rrs []dns.RR, i int) []dns.RR {
if i >= len(rrs) {
return rrs
}
rrs = append(rrs[:i], rrs[i+1:]...)
return rrs
}

59
plugin/file/tree/less.go Normal file
View File

@@ -0,0 +1,59 @@
package tree
import (
"bytes"
"github.com/miekg/dns"
)
// less returns <0 when a is less than b, 0 when they are equal and
// >0 when a is larger than b.
// The function orders names in DNSSEC canonical order: RFC 4034s section-6.1
//
// See http://bert-hubert.blogspot.co.uk/2015/10/how-to-do-fast-canonical-ordering-of.html
// for a blog article on this implementation, although here we still go label by label.
//
// The values of a and b are *not* lowercased before the comparison!
func less(a, b string) int {
i := 1
aj := len(a)
bj := len(b)
for {
ai, oka := dns.PrevLabel(a, i)
bi, okb := dns.PrevLabel(b, i)
if oka && okb {
return 0
}
// sadly this []byte will allocate... TODO(miek): check if this is needed
// for a name, otherwise compare the strings.
ab := []byte(a[ai:aj])
bb := []byte(b[bi:bj])
doDDD(ab)
doDDD(bb)
res := bytes.Compare(ab, bb)
if res != 0 {
return res
}
i++
aj, bj = ai, bi
}
}
func doDDD(b []byte) {
lb := len(b)
for i := 0; i < lb; i++ {
if i+3 < lb && b[i] == '\\' && isDigit(b[i+1]) && isDigit(b[i+2]) && isDigit(b[i+3]) {
b[i] = dddToByte(b[i:])
for j := i + 1; j < lb-3; j++ {
b[j] = b[j+3]
}
lb -= 3
}
}
}
func isDigit(b byte) bool { return b >= '0' && b <= '9' }
func dddToByte(s []byte) byte { return (s[1]-'0')*100 + (s[2]-'0')*10 + (s[3] - '0') }

View File

@@ -0,0 +1,81 @@
package tree
import (
"sort"
"strings"
"testing"
)
type set []string
func (p set) Len() int { return len(p) }
func (p set) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p set) Less(i, j int) bool { d := less(p[i], p[j]); return d <= 0 }
func TestLess(t *testing.T) {
tests := []struct {
in []string
out []string
}{
{
[]string{"aaa.powerdns.de", "bbb.powerdns.net.", "xxx.powerdns.com."},
[]string{"xxx.powerdns.com.", "aaa.powerdns.de", "bbb.powerdns.net."},
},
{
[]string{"aaa.POWERDNS.de", "bbb.PoweRdnS.net.", "xxx.powerdns.com."},
[]string{"xxx.powerdns.com.", "aaa.POWERDNS.de", "bbb.PoweRdnS.net."},
},
{
[]string{"aaa.aaaa.aa.", "aa.aaa.a.", "bbb.bbbb.bb."},
[]string{"aa.aaa.a.", "aaa.aaaa.aa.", "bbb.bbbb.bb."},
},
{
[]string{"aaaaa.", "aaa.", "bbb."},
[]string{"aaa.", "aaaaa.", "bbb."},
},
{
[]string{"a.a.a.a.", "a.a.", "a.a.a."},
[]string{"a.a.", "a.a.a.", "a.a.a.a."},
},
{
[]string{"example.", "z.example.", "a.example."},
[]string{"example.", "a.example.", "z.example."},
},
{
[]string{"a.example.", "Z.a.example.", "z.example.", "yljkjljk.a.example.", "\\001.z.example.", "example.", "*.z.example.", "\\200.z.example.", "zABC.a.EXAMPLE."},
[]string{"example.", "a.example.", "yljkjljk.a.example.", "Z.a.example.", "zABC.a.EXAMPLE.", "z.example.", "\\001.z.example.", "*.z.example.", "\\200.z.example."},
},
{
// RFC3034 example.
[]string{"a.example.", "Z.a.example.", "z.example.", "yljkjljk.a.example.", "example.", "*.z.example.", "zABC.a.EXAMPLE."},
[]string{"example.", "a.example.", "yljkjljk.a.example.", "Z.a.example.", "zABC.a.EXAMPLE.", "z.example.", "*.z.example."},
},
}
Tests:
for j, test := range tests {
// Need to lowercase these example as the Less function does lowercase for us anymore.
for i, b := range test.in {
test.in[i] = strings.ToLower(b)
}
for i, b := range test.out {
test.out[i] = strings.ToLower(b)
}
sort.Sort(set(test.in))
for i := 0; i < len(test.in); i++ {
if test.in[i] != test.out[i] {
t.Errorf("Test %d: expected %s, got %s\n", j, test.out[i], test.in[i])
n := ""
for k, in := range test.in {
if k+1 == len(test.in) {
n = "\n"
}
t.Logf("%s <-> %s\n%s", in, test.out[k], n)
}
continue Tests
}
}
}
}

62
plugin/file/tree/print.go Normal file
View File

@@ -0,0 +1,62 @@
package tree
import "fmt"
// Print prints a Tree. Main use is to aid in debugging.
func (t *Tree) Print() {
if t.Root == nil {
fmt.Println("<nil>")
}
t.Root.print()
}
func (n *Node) print() {
q := newQueue()
q.push(n)
nodesInCurrentLevel := 1
nodesInNextLevel := 0
for !q.empty() {
do := q.pop()
nodesInCurrentLevel--
if do != nil {
fmt.Print(do.Elem.Name(), " ")
q.push(do.Left)
q.push(do.Right)
nodesInNextLevel += 2
}
if nodesInCurrentLevel == 0 {
fmt.Println()
}
nodesInCurrentLevel = nodesInNextLevel
nodesInNextLevel = 0
}
fmt.Println()
}
type queue []*Node
// newQueue returns a new queue.
func newQueue() queue {
q := queue([]*Node{})
return q
}
// push pushes n to the end of the queue.
func (q *queue) push(n *Node) {
*q = append(*q, n)
}
// pop pops the first element off the queue.
func (q *queue) pop() *Node {
n := (*q)[0]
*q = (*q)[1:]
return n
}
// empty returns true when the queue contains zero nodes.
func (q *queue) empty() bool {
return len(*q) == 0
}

455
plugin/file/tree/tree.go Normal file
View File

@@ -0,0 +1,455 @@
// Copyright ©2012 The bíogo Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found at the end of this file.
// Package tree implements Left-Leaning Red Black trees as described by Robert Sedgewick.
//
// More details relating to the implementation are available at the following locations:
//
// http://www.cs.princeton.edu/~rs/talks/LLRB/LLRB.pdf
// http://www.cs.princeton.edu/~rs/talks/LLRB/Java/RedBlackBST.java
// http://www.teachsolaisgames.com/articles/balanced_left_leaning.html
//
// Heavily modified by Miek Gieben for use in DNS zones.
package tree
import "github.com/miekg/dns"
const (
td234 = iota
bu23
)
// Operation mode of the LLRB tree.
const mode = bu23
func init() {
if mode != td234 && mode != bu23 {
panic("tree: unknown mode")
}
}
// A Color represents the color of a Node.
type Color bool
const (
// Red as false give us the defined behaviour that new nodes are red. Although this
// is incorrect for the root node, that is resolved on the first insertion.
red Color = false
black Color = true
)
// A Node represents a node in the LLRB tree.
type Node struct {
Elem *Elem
Left, Right *Node
Color Color
}
// A Tree manages the root node of an LLRB tree. Public methods are exposed through this type.
type Tree struct {
Root *Node // Root node of the tree.
Count int // Number of elements stored.
}
// Helper methods
// color returns the effect color of a Node. A nil node returns black.
func (n *Node) color() Color {
if n == nil {
return black
}
return n.Color
}
// (a,c)b -rotL-> ((a,)b,)c
func (n *Node) rotateLeft() (root *Node) {
// Assumes: n has two children.
root = n.Right
n.Right = root.Left
root.Left = n
root.Color = n.Color
n.Color = red
return
}
// (a,c)b -rotR-> (,(,c)b)a
func (n *Node) rotateRight() (root *Node) {
// Assumes: n has two children.
root = n.Left
n.Left = root.Right
root.Right = n
root.Color = n.Color
n.Color = red
return
}
// (aR,cR)bB -flipC-> (aB,cB)bR | (aB,cB)bR -flipC-> (aR,cR)bB
func (n *Node) flipColors() {
// Assumes: n has two children.
n.Color = !n.Color
n.Left.Color = !n.Left.Color
n.Right.Color = !n.Right.Color
}
// fixUp ensures that black link balance is correct, that red nodes lean left,
// and that 4 nodes are split in the case of BU23 and properly balanced in TD234.
func (n *Node) fixUp() *Node {
if n.Right.color() == red {
if mode == td234 && n.Right.Left.color() == red {
n.Right = n.Right.rotateRight()
}
n = n.rotateLeft()
}
if n.Left.color() == red && n.Left.Left.color() == red {
n = n.rotateRight()
}
if mode == bu23 && n.Left.color() == red && n.Right.color() == red {
n.flipColors()
}
return n
}
func (n *Node) moveRedLeft() *Node {
n.flipColors()
if n.Right.Left.color() == red {
n.Right = n.Right.rotateRight()
n = n.rotateLeft()
n.flipColors()
if mode == td234 && n.Right.Right.color() == red {
n.Right = n.Right.rotateLeft()
}
}
return n
}
func (n *Node) moveRedRight() *Node {
n.flipColors()
if n.Left.Left.color() == red {
n = n.rotateRight()
n.flipColors()
}
return n
}
// Len returns the number of elements stored in the Tree.
func (t *Tree) Len() int {
return t.Count
}
// Search returns the first match of qname in the Tree.
func (t *Tree) Search(qname string) (*Elem, bool) {
if t.Root == nil {
return nil, false
}
n, res := t.Root.search(qname)
if n == nil {
return nil, res
}
return n.Elem, res
}
// search searches the tree for qname and type.
func (n *Node) search(qname string) (*Node, bool) {
for n != nil {
switch c := Less(n.Elem, qname); {
case c == 0:
return n, true
case c < 0:
n = n.Left
default:
n = n.Right
}
}
return n, false
}
// Insert inserts rr into the Tree at the first match found
// with e or when a nil node is reached.
func (t *Tree) Insert(rr dns.RR) {
var d int
t.Root, d = t.Root.insert(rr)
t.Count += d
t.Root.Color = black
}
// insert inserts rr in to the tree.
func (n *Node) insert(rr dns.RR) (root *Node, d int) {
if n == nil {
return &Node{Elem: newElem(rr)}, 1
} else if n.Elem == nil {
n.Elem = newElem(rr)
return n, 1
}
if mode == td234 {
if n.Left.color() == red && n.Right.color() == red {
n.flipColors()
}
}
switch c := Less(n.Elem, rr.Header().Name); {
case c == 0:
n.Elem.Insert(rr)
case c < 0:
n.Left, d = n.Left.insert(rr)
default:
n.Right, d = n.Right.insert(rr)
}
if n.Right.color() == red && n.Left.color() == black {
n = n.rotateLeft()
}
if n.Left.color() == red && n.Left.Left.color() == red {
n = n.rotateRight()
}
if mode == bu23 {
if n.Left.color() == red && n.Right.color() == red {
n.flipColors()
}
}
root = n
return
}
// DeleteMin deletes the node with the minimum value in the tree.
func (t *Tree) DeleteMin() {
if t.Root == nil {
return
}
var d int
t.Root, d = t.Root.deleteMin()
t.Count += d
if t.Root == nil {
return
}
t.Root.Color = black
}
func (n *Node) deleteMin() (root *Node, d int) {
if n.Left == nil {
return nil, -1
}
if n.Left.color() == black && n.Left.Left.color() == black {
n = n.moveRedLeft()
}
n.Left, d = n.Left.deleteMin()
root = n.fixUp()
return
}
// DeleteMax deletes the node with the maximum value in the tree.
func (t *Tree) DeleteMax() {
if t.Root == nil {
return
}
var d int
t.Root, d = t.Root.deleteMax()
t.Count += d
if t.Root == nil {
return
}
t.Root.Color = black
}
func (n *Node) deleteMax() (root *Node, d int) {
if n.Left != nil && n.Left.color() == red {
n = n.rotateRight()
}
if n.Right == nil {
return nil, -1
}
if n.Right.color() == black && n.Right.Left.color() == black {
n = n.moveRedRight()
}
n.Right, d = n.Right.deleteMax()
root = n.fixUp()
return
}
// Delete removes rr from the tree, is the node turns empty, that node is deleted with DeleteNode.
func (t *Tree) Delete(rr dns.RR) {
if t.Root == nil {
return
}
el, _ := t.Search(rr.Header().Name)
if el == nil {
t.deleteNode(rr)
return
}
// Delete from this element.
empty := el.Delete(rr)
if empty {
t.deleteNode(rr)
return
}
}
// DeleteNode deletes the node that matches rr according to Less().
func (t *Tree) deleteNode(rr dns.RR) {
if t.Root == nil {
return
}
var d int
t.Root, d = t.Root.delete(rr)
t.Count += d
if t.Root == nil {
return
}
t.Root.Color = black
}
func (n *Node) delete(rr dns.RR) (root *Node, d int) {
if Less(n.Elem, rr.Header().Name) < 0 {
if n.Left != nil {
if n.Left.color() == black && n.Left.Left.color() == black {
n = n.moveRedLeft()
}
n.Left, d = n.Left.delete(rr)
}
} else {
if n.Left.color() == red {
n = n.rotateRight()
}
if n.Right == nil && Less(n.Elem, rr.Header().Name) == 0 {
return nil, -1
}
if n.Right != nil {
if n.Right.color() == black && n.Right.Left.color() == black {
n = n.moveRedRight()
}
if Less(n.Elem, rr.Header().Name) == 0 {
n.Elem = n.Right.min().Elem
n.Right, d = n.Right.deleteMin()
} else {
n.Right, d = n.Right.delete(rr)
}
}
}
root = n.fixUp()
return
}
// Min returns the minimum value stored in the tree.
func (t *Tree) Min() *Elem {
if t.Root == nil {
return nil
}
return t.Root.min().Elem
}
func (n *Node) min() *Node {
for ; n.Left != nil; n = n.Left {
}
return n
}
// Max returns the maximum value stored in the tree.
func (t *Tree) Max() *Elem {
if t.Root == nil {
return nil
}
return t.Root.max().Elem
}
func (n *Node) max() *Node {
for ; n.Right != nil; n = n.Right {
}
return n
}
// Prev returns the greatest value equal to or less than the qname according to Less().
func (t *Tree) Prev(qname string) (*Elem, bool) {
if t.Root == nil {
return nil, false
}
n := t.Root.floor(qname)
if n == nil {
return nil, false
}
return n.Elem, true
}
func (n *Node) floor(qname string) *Node {
if n == nil {
return nil
}
switch c := Less(n.Elem, qname); {
case c == 0:
return n
case c <= 0:
return n.Left.floor(qname)
default:
if r := n.Right.floor(qname); r != nil {
return r
}
}
return n
}
// Next returns the smallest value equal to or greater than the qname according to Less().
func (t *Tree) Next(qname string) (*Elem, bool) {
if t.Root == nil {
return nil, false
}
n := t.Root.ceil(qname)
if n == nil {
return nil, false
}
return n.Elem, true
}
func (n *Node) ceil(qname string) *Node {
if n == nil {
return nil
}
switch c := Less(n.Elem, qname); {
case c == 0:
return n
case c > 0:
return n.Right.ceil(qname)
default:
if l := n.Left.ceil(qname); l != nil {
return l
}
}
return n
}
/*
Copyright ©2012 The bíogo Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the bíogo project nor the names of its authors and
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

13
plugin/file/wildcard.go Normal file
View File

@@ -0,0 +1,13 @@
package file
import "github.com/miekg/dns"
// replaceWithWildcard replaces the left most label with '*'.
func replaceWithAsteriskLabel(qname string) (wildcard string) {
i, shot := dns.NextLabel(qname, 0)
if shot {
return ""
}
return "*." + qname[i:]
}

View File

@@ -0,0 +1,289 @@
package file
import (
"strings"
"testing"
"github.com/coredns/coredns/plugin/pkg/dnsrecorder"
"github.com/coredns/coredns/plugin/test"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
var wildcardTestCases = []test.Case{
{
Qname: "wild.dnssex.nl.", Qtype: dns.TypeTXT,
Answer: []dns.RR{
test.TXT(`wild.dnssex.nl. 1800 IN TXT "Doing It Safe Is Better"`),
},
Ns: dnssexAuth[:len(dnssexAuth)-1], // remove RRSIG on the end
},
{
Qname: "a.wild.dnssex.nl.", Qtype: dns.TypeTXT,
Answer: []dns.RR{
test.TXT(`a.wild.dnssex.nl. 1800 IN TXT "Doing It Safe Is Better"`),
},
Ns: dnssexAuth[:len(dnssexAuth)-1], // remove RRSIG on the end
},
{
Qname: "wild.dnssex.nl.", Qtype: dns.TypeTXT, Do: true,
Answer: []dns.RR{
test.RRSIG("wild.dnssex.nl. 1800 IN RRSIG TXT 8 2 1800 20160428190224 20160329190224 14460 dnssex.nl. FUZSTyvZfeuuOpCm"),
test.TXT(`wild.dnssex.nl. 1800 IN TXT "Doing It Safe Is Better"`),
},
Ns: append([]dns.RR{
test.NSEC("a.dnssex.nl. 14400 IN NSEC www.dnssex.nl. A AAAA RRSIG NSEC"),
test.RRSIG("a.dnssex.nl. 14400 IN RRSIG NSEC 8 3 14400 20160428190224 20160329190224 14460 dnssex.nl. S+UMs2ySgRaaRY"),
}, dnssexAuth...),
Extra: []dns.RR{test.OPT(4096, true)},
},
{
Qname: "a.wild.dnssex.nl.", Qtype: dns.TypeTXT, Do: true,
Answer: []dns.RR{
test.RRSIG("a.wild.dnssex.nl. 1800 IN RRSIG TXT 8 2 1800 20160428190224 20160329190224 14460 dnssex.nl. FUZSTyvZfeuuOpCm"),
test.TXT(`a.wild.dnssex.nl. 1800 IN TXT "Doing It Safe Is Better"`),
},
Ns: append([]dns.RR{
test.NSEC("a.dnssex.nl. 14400 IN NSEC www.dnssex.nl. A AAAA RRSIG NSEC"),
test.RRSIG("a.dnssex.nl. 14400 IN RRSIG NSEC 8 3 14400 20160428190224 20160329190224 14460 dnssex.nl. S+UMs2ySgRaaRY"),
}, dnssexAuth...),
Extra: []dns.RR{test.OPT(4096, true)},
},
// nodata responses
{
Qname: "wild.dnssex.nl.", Qtype: dns.TypeSRV,
Ns: []dns.RR{
test.SOA(`dnssex.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1459281744 14400 3600 604800 14400`),
},
},
{
Qname: "wild.dnssex.nl.", Qtype: dns.TypeSRV, Do: true,
Ns: []dns.RR{
// TODO(miek): needs closest encloser proof as well? This is the wrong answer
test.NSEC(`*.dnssex.nl. 14400 IN NSEC a.dnssex.nl. TXT RRSIG NSEC`),
test.RRSIG(`*.dnssex.nl. 14400 IN RRSIG NSEC 8 2 14400 20160428190224 20160329190224 14460 dnssex.nl. os6INm6q2eXknD5z8TaaDOV+Ge/Ko+2dXnKP+J1fqJzafXJVH1F0nDrcXmMlR6jlBHA=`),
test.RRSIG(`dnssex.nl. 1800 IN RRSIG SOA 8 2 1800 20160428190224 20160329190224 14460 dnssex.nl. CA/Y3m9hCOiKC/8ieSOv8SeP964Bq++lyH8BZJcTaabAsERs4xj5PRtcxicwQXZiF8fYUCpROlUS0YR8Cdw=`),
test.SOA(`dnssex.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1459281744 14400 3600 604800 14400`),
},
Extra: []dns.RR{test.OPT(4096, true)},
},
}
var dnssexAuth = []dns.RR{
test.NS("dnssex.nl. 1800 IN NS linode.atoom.net."),
test.NS("dnssex.nl. 1800 IN NS ns-ext.nlnetlabs.nl."),
test.NS("dnssex.nl. 1800 IN NS omval.tednet.nl."),
test.RRSIG("dnssex.nl. 1800 IN RRSIG NS 8 2 1800 20160428190224 20160329190224 14460 dnssex.nl. dLIeEvP86jj5ndkcLzhgvWixTABjWAGRTGQsPsVDFXsGMf9TGGC9FEomgkCVeNC0="),
}
func TestLookupWildcard(t *testing.T) {
zone, err := Parse(strings.NewReader(dbDnssexNLSigned), testzone1, "stdin", 0)
if err != nil {
t.Fatalf("Expect no error when reading zone, got %q", err)
}
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone1: zone}, Names: []string{testzone1}}}
ctx := context.TODO()
for _, tc := range wildcardTestCases {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := fm.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("Expected no error, got %v\n", err)
return
}
resp := rec.Msg
test.SortAndCheck(t, resp, tc)
}
}
var wildcardDoubleTestCases = []test.Case{
{
Qname: "wild.w.example.org.", Qtype: dns.TypeTXT,
Answer: []dns.RR{
test.TXT(`wild.w.example.org. IN TXT "Wildcard"`),
},
Ns: exampleAuth,
},
{
Qname: "wild.c.example.org.", Qtype: dns.TypeTXT,
Answer: []dns.RR{
test.TXT(`wild.c.example.org. IN TXT "c Wildcard"`),
},
Ns: exampleAuth,
},
{
Qname: "wild.d.example.org.", Qtype: dns.TypeTXT,
Answer: []dns.RR{
test.TXT(`alias.example.org. IN TXT "Wildcard CNAME expansion"`),
test.CNAME(`wild.d.example.org. IN CNAME alias.example.org`),
},
Ns: exampleAuth,
},
{
Qname: "alias.example.org.", Qtype: dns.TypeTXT,
Answer: []dns.RR{
test.TXT(`alias.example.org. IN TXT "Wildcard CNAME expansion"`),
},
Ns: exampleAuth,
},
}
var exampleAuth = []dns.RR{
test.NS("example.org. 3600 IN NS a.iana-servers.net."),
test.NS("example.org. 3600 IN NS b.iana-servers.net."),
}
func TestLookupDoubleWildcard(t *testing.T) {
zone, err := Parse(strings.NewReader(exampleOrg), "example.org.", "stdin", 0)
if err != nil {
t.Fatalf("Expect no error when reading zone, got %q", err)
}
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{"example.org.": zone}, Names: []string{"example.org."}}}
ctx := context.TODO()
for _, tc := range wildcardDoubleTestCases {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := fm.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("Expected no error, got %v\n", err)
return
}
resp := rec.Msg
test.SortAndCheck(t, resp, tc)
}
}
func TestReplaceWithAsteriskLabel(t *testing.T) {
tests := []struct {
in, out string
}{
{".", ""},
{"miek.nl.", "*.nl."},
{"www.miek.nl.", "*.miek.nl."},
}
for _, tc := range tests {
got := replaceWithAsteriskLabel(tc.in)
if got != tc.out {
t.Errorf("Expected to be %s, got %s", tc.out, got)
}
}
}
var apexWildcardTestCases = []test.Case{
{
Qname: "foo.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{test.A(`foo.example.org. 3600 IN A 127.0.0.54`)},
Ns: []dns.RR{test.NS(`example.org. 3600 IN NS b.iana-servers.net.`)},
},
{
Qname: "bar.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{test.A(`bar.example.org. 3600 IN A 127.0.0.53`)},
Ns: []dns.RR{test.NS(`example.org. 3600 IN NS b.iana-servers.net.`)},
},
}
func TestLookupApexWildcard(t *testing.T) {
const name = "example.org."
zone, err := Parse(strings.NewReader(apexWildcard), name, "stdin", 0)
if err != nil {
t.Fatalf("Expect no error when reading zone, got %q", err)
}
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{name: zone}, Names: []string{name}}}
ctx := context.TODO()
for _, tc := range apexWildcardTestCases {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := fm.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("Expected no error, got %v\n", err)
return
}
resp := rec.Msg
test.SortAndCheck(t, resp, tc)
}
}
var multiWildcardTestCases = []test.Case{
{
Qname: "foo.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{test.A(`foo.example.org. 3600 IN A 127.0.0.54`)},
Ns: []dns.RR{test.NS(`example.org. 3600 IN NS b.iana-servers.net.`)},
},
{
Qname: "bar.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{test.A(`bar.example.org. 3600 IN A 127.0.0.53`)},
Ns: []dns.RR{test.NS(`example.org. 3600 IN NS b.iana-servers.net.`)},
},
{
Qname: "bar.intern.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{test.A(`bar.intern.example.org. 3600 IN A 127.0.1.52`)},
Ns: []dns.RR{test.NS(`example.org. 3600 IN NS b.iana-servers.net.`)},
},
}
func TestLookupMultiWildcard(t *testing.T) {
const name = "example.org."
zone, err := Parse(strings.NewReader(doubleWildcard), name, "stdin", 0)
if err != nil {
t.Fatalf("Expect no error when reading zone, got %q", err)
}
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{name: zone}, Names: []string{name}}}
ctx := context.TODO()
for _, tc := range multiWildcardTestCases {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := fm.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("Expected no error, got %v\n", err)
return
}
resp := rec.Msg
test.SortAndCheck(t, resp, tc)
}
}
const exampleOrg = `; example.org test file
example.org. IN SOA sns.dns.icann.org. noc.dns.icann.org. 2015082541 7200 3600 1209600 3600
example.org. IN NS b.iana-servers.net.
example.org. IN NS a.iana-servers.net.
example.org. IN A 127.0.0.1
example.org. IN A 127.0.0.2
*.w.example.org. IN TXT "Wildcard"
a.b.c.w.example.org. IN TXT "Not a wildcard"
*.c.example.org. IN TXT "c Wildcard"
*.d.example.org. IN CNAME alias.example.org.
alias.example.org. IN TXT "Wildcard CNAME expansion"
`
const apexWildcard = `; example.org test file with wildcard at apex
example.org. IN SOA sns.dns.icann.org. noc.dns.icann.org. 2015082541 7200 3600 1209600 3600
example.org. IN NS b.iana-servers.net.
*.example.org. IN A 127.0.0.53
foo.example.org. IN A 127.0.0.54
`
const doubleWildcard = `; example.org test file with wildcard at apex
example.org. IN SOA sns.dns.icann.org. noc.dns.icann.org. 2015082541 7200 3600 1209600 3600
example.org. IN NS b.iana-servers.net.
*.example.org. IN A 127.0.0.53
*.intern.example.org. IN A 127.0.1.52
foo.example.org. IN A 127.0.0.54
`

62
plugin/file/xfr.go Normal file
View File

@@ -0,0 +1,62 @@
package file
import (
"fmt"
"log"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
// Xfr serves up an AXFR.
type Xfr struct {
*Zone
}
// ServeDNS implements the plugin.Handler interface.
func (x Xfr) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
state := request.Request{W: w, Req: r}
if !x.TransferAllowed(state) {
return dns.RcodeServerFailure, nil
}
if state.QType() != dns.TypeAXFR && state.QType() != dns.TypeIXFR {
return 0, plugin.Error(x.Name(), fmt.Errorf("xfr called with non transfer type: %d", state.QType()))
}
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]) // add closing SOA to the end
log.Printf("[INFO] Outgoing transfer of %d records of zone %s to %s started", len(records), x.origin, state.IP())
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
}
// Name implements the plugin.Hander interface.
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.

34
plugin/file/xfr_test.go Normal file
View File

@@ -0,0 +1,34 @@
package file
import (
"fmt"
"strings"
)
func ExampleZone_All() {
zone, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin", 0)
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
}

190
plugin/file/zone.go Normal file
View File

@@ -0,0 +1,190 @@
package file
import (
"fmt"
"net"
"path"
"strings"
"sync"
"github.com/coredns/coredns/plugin/file/tree"
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
// Zone defines a structure that contains all data related to a DNS zone.
type Zone struct {
origin string
origLen int
file string
*tree.Tree
Apex Apex
TransferTo []string
StartupOnce sync.Once
TransferFrom []string
Expired *bool
NoReload bool
reloadMu sync.RWMutex
ReloadShutdown chan bool
Proxy proxy.Proxy // Proxy for looking up names during the resolution process
}
// Apex contains the apex records of a zone: SOA, NS and their potential signatures.
type Apex struct {
SOA *dns.SOA
NS []dns.RR
SIGSOA []dns.RR
SIGNS []dns.RR
}
// NewZone returns a new zone.
func NewZone(name, file string) *Zone {
z := &Zone{
origin: dns.Fqdn(name),
origLen: dns.CountLabel(dns.Fqdn(name)),
file: path.Clean(file),
Tree: &tree.Tree{},
Expired: new(bool),
ReloadShutdown: make(chan bool),
}
*z.Expired = false
return z
}
// Copy copies a zone.
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
}
// Insert inserts r into z.
func (z *Zone) Insert(r dns.RR) error {
r.Header().Name = strings.ToLower(r.Header().Name)
switch h := r.Header().Rrtype; h {
case dns.TypeNS:
r.(*dns.NS).Ns = strings.ToLower(r.(*dns.NS).Ns)
if r.Header().Name == z.origin {
z.Apex.NS = append(z.Apex.NS, r)
return nil
}
case dns.TypeSOA:
r.(*dns.SOA).Ns = strings.ToLower(r.(*dns.SOA).Ns)
r.(*dns.SOA).Mbox = strings.ToLower(r.(*dns.SOA).Mbox)
z.Apex.SOA = r.(*dns.SOA)
return nil
case dns.TypeNSEC3, dns.TypeNSEC3PARAM:
return fmt.Errorf("NSEC3 zone is not supported, dropping RR: %s for zone: %s", r.Header().Name, z.origin)
case dns.TypeRRSIG:
x := r.(*dns.RRSIG)
switch x.TypeCovered {
case dns.TypeSOA:
z.Apex.SIGSOA = append(z.Apex.SIGSOA, x)
return nil
case dns.TypeNS:
if r.Header().Name == z.origin {
z.Apex.SIGNS = append(z.Apex.SIGNS, x)
return nil
}
}
case dns.TypeCNAME:
r.(*dns.CNAME).Target = strings.ToLower(r.(*dns.CNAME).Target)
case dns.TypeMX:
r.(*dns.MX).Mx = strings.ToLower(r.(*dns.MX).Mx)
case dns.TypeSRV:
r.(*dns.SRV).Target = strings.ToLower(r.(*dns.SRV).Target)
}
z.Tree.Insert(r)
return nil
}
// Delete deletes r from z.
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(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
}
// 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 {
if !z.NoReload {
z.reloadMu.RLock()
defer z.reloadMu.RUnlock()
}
records := []dns.RR{}
allNodes := z.Tree.All()
for _, a := range allNodes {
records = append(records, a.All()...)
}
if len(z.Apex.SIGNS) > 0 {
records = append(z.Apex.SIGNS, records...)
}
records = append(z.Apex.NS, records...)
if len(z.Apex.SIGSOA) > 0 {
records = append(z.Apex.SIGSOA, records...)
}
return append([]dns.RR{z.Apex.SOA}, records...)
}
// Print prints the zone's tree to stdout.
func (z *Zone) Print() {
z.Tree.Print()
}
// NameFromRight returns the labels from the right, staring with the
// origin and then i labels extra. When we are overshooting the name
// the returned boolean is set to true.
func (z *Zone) nameFromRight(qname string, i int) (string, bool) {
if i <= 0 {
return z.origin, false
}
for j := 1; j <= z.origLen; j++ {
if _, shot := dns.PrevLabel(qname, j); shot {
return qname, shot
}
}
k := 0
shot := false
for j := 1; j <= i; j++ {
k, shot = dns.PrevLabel(qname, j+z.origLen)
if shot {
return qname, shot
}
}
return qname[k:], false
}

30
plugin/file/zone_test.go Normal file
View File

@@ -0,0 +1,30 @@
package file
import "testing"
func TestNameFromRight(t *testing.T) {
z := NewZone("example.org.", "stdin")
tests := []struct {
in string
labels int
shot bool
expected string
}{
{"example.org.", 0, false, "example.org."},
{"a.example.org.", 0, false, "example.org."},
{"a.example.org.", 1, false, "a.example.org."},
{"a.example.org.", 2, true, "a.example.org."},
{"a.b.example.org.", 2, false, "a.b.example.org."},
}
for i, tc := range tests {
got, shot := z.nameFromRight(tc.in, tc.labels)
if got != tc.expected {
t.Errorf("Test %d: expected %s, got %s\n", i, tc.expected, got)
}
if shot != tc.shot {
t.Errorf("Test %d: expected shot to be %t, got %t\n", i, tc.shot, shot)
}
}
}