mirror of
https://github.com/coredns/coredns.git
synced 2026-03-25 06:15:32 -04:00
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:
55
plugin/file/README.md
Normal file
55
plugin/file/README.md
Normal 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
24
plugin/file/closest.go
Normal 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)
|
||||
}
|
||||
38
plugin/file/closest_test.go
Normal file
38
plugin/file/closest_test.go
Normal 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
124
plugin/file/cname_test.go
Normal 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.`
|
||||
207
plugin/file/delegation_test.go
Normal file
207
plugin/file/delegation_test.go
Normal 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
44
plugin/file/dname.go
Normal 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
300
plugin/file/dname_test.go
Normal 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
358
plugin/file/dnssec_test.go
Normal 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
145
plugin/file/dnssex_test.go
Normal 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
75
plugin/file/ds_test.go
Normal 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
159
plugin/file/ent_test.go
Normal 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
113
plugin/file/example_org.go
Normal 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
138
plugin/file/file.go
Normal 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
31
plugin/file/file_test.go
Normal 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
253
plugin/file/glue_test.go
Normal 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."
|
||||
32
plugin/file/include_test.go
Normal file
32
plugin/file/include_test.go
Normal 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
467
plugin/file/lookup.go
Normal 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
194
plugin/file/lookup_test.go
Normal 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
82
plugin/file/notify.go
Normal 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
28
plugin/file/nsec3_test.go
Normal 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
72
plugin/file/reload.go
Normal 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
|
||||
}
|
||||
82
plugin/file/reload_test.go
Normal file
82
plugin/file/reload_test.go
Normal 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
199
plugin/file/secondary.go
Normal 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
|
||||
168
plugin/file/secondary_test.go
Normal file
168
plugin/file/secondary_test.go
Normal 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
171
plugin/file/setup.go
Normal 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
77
plugin/file/setup_test.go
Normal 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
48
plugin/file/tree/all.go
Normal 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
136
plugin/file/tree/elem.go
Normal 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
59
plugin/file/tree/less.go
Normal 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') }
|
||||
81
plugin/file/tree/less_test.go
Normal file
81
plugin/file/tree/less_test.go
Normal 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
62
plugin/file/tree/print.go
Normal 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
455
plugin/file/tree/tree.go
Normal 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
13
plugin/file/wildcard.go
Normal 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:]
|
||||
}
|
||||
289
plugin/file/wildcard_test.go
Normal file
289
plugin/file/wildcard_test.go
Normal 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
62
plugin/file/xfr.go
Normal 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
34
plugin/file/xfr_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ExampleZone_All() {
|
||||
zone, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin", 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
190
plugin/file/zone.go
Normal 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
30
plugin/file/zone_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user