mirror of
https://github.com/coredns/coredns.git
synced 2025-10-31 18:23:13 -04:00
middleware/file: fix delegations (#376)
Fix the delegation handling in the *file* and *dnssec* middleware. Refactor tests a bit and show that they are failling. Add a Tree printer, cleanups and tests. Fix wildcard test - should get no answer from empty-non-terminal
This commit is contained in:
@@ -1,66 +1,24 @@
|
|||||||
package file
|
package file
|
||||||
|
|
||||||
import "github.com/miekg/dns"
|
import (
|
||||||
|
"github.com/miekg/coredns/middleware/file/tree"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
// ClosestEncloser returns the closest encloser for rr.
|
// ClosestEncloser returns the closest encloser for rr.
|
||||||
func (z *Zone) ClosestEncloser(qname string, qtype uint16) string {
|
func (z *Zone) ClosestEncloser(qname string) (*tree.Elem, bool) {
|
||||||
// tree/tree.go does not store a parent *Node pointer, so we can't
|
|
||||||
// just follow up the tree. TODO(miek): fix.
|
|
||||||
offset, end := dns.NextLabel(qname, 0)
|
offset, end := dns.NextLabel(qname, 0)
|
||||||
for !end {
|
for !end {
|
||||||
elem, _ := z.Tree.Search(qname, qtype)
|
elem, _ := z.Tree.Search(qname)
|
||||||
if elem != nil {
|
if elem != nil {
|
||||||
return elem.Name()
|
return elem, true
|
||||||
}
|
}
|
||||||
qname = qname[offset:]
|
qname = qname[offset:]
|
||||||
|
|
||||||
offset, end = dns.NextLabel(qname, offset)
|
offset, end = dns.NextLabel(qname, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
return z.Apex.SOA.Header().Name
|
return z.Tree.Search(z.origin)
|
||||||
}
|
|
||||||
|
|
||||||
// nameErrorProof finds the closest encloser and return an NSEC that proofs
|
|
||||||
// the wildcard does not exist and an NSEC that proofs the name does no exist.
|
|
||||||
func (z *Zone) nameErrorProof(qname string, qtype uint16) []dns.RR {
|
|
||||||
elem := z.Tree.Prev(qname)
|
|
||||||
if elem == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
nsec := z.lookupNSEC(elem, true)
|
|
||||||
nsecIndex := 0
|
|
||||||
for i := 0; i < len(nsec); i++ {
|
|
||||||
if nsec[i].Header().Rrtype == dns.TypeNSEC {
|
|
||||||
nsecIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We do this lookup twice, once for wildcard and once for the name proof. TODO(miek): fix
|
|
||||||
ce := z.ClosestEncloser(qname, qtype)
|
|
||||||
elem = z.Tree.Prev("*." + ce)
|
|
||||||
if elem == nil {
|
|
||||||
// Root?
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
nsec1 := z.lookupNSEC(elem, true)
|
|
||||||
nsec1Index := 0
|
|
||||||
for i := 0; i < len(nsec1); i++ {
|
|
||||||
if nsec1[i].Header().Rrtype == dns.TypeNSEC {
|
|
||||||
nsec1Index = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(nsec) == 0 || len(nsec1) == 0 {
|
|
||||||
return nsec
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for duplicate NSEC.
|
|
||||||
if nsec[nsecIndex].Header().Name == nsec1[nsec1Index].Header().Name &&
|
|
||||||
nsec[nsecIndex].(*dns.NSEC).NextDomain == nsec1[nsec1Index].(*dns.NSEC).NextDomain {
|
|
||||||
return nsec
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(nsec, nsec1...)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ package file
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestClosestEncloser(t *testing.T) {
|
func TestClosestEncloser(t *testing.T) {
|
||||||
@@ -26,9 +24,15 @@ func TestClosestEncloser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
ce := z.ClosestEncloser(tc.in, dns.TypeA)
|
ce, _ := z.ClosestEncloser(tc.in)
|
||||||
if ce != tc.out {
|
if ce == nil {
|
||||||
t.Errorf("expected ce to be %s for %s, got %s", tc.out, tc.in, ce)
|
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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,32 @@ var delegationTestCases = []test.Case{
|
|||||||
test.NS("delegated.miek.nl. 1800 IN NS a.delegated.miek.nl."),
|
test.NS("delegated.miek.nl. 1800 IN NS a.delegated.miek.nl."),
|
||||||
test.NS("delegated.miek.nl. 1800 IN NS ns-ext.nlnetlabs.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,
|
Qname: "miek.nl.", Qtype: dns.TypeSOA,
|
||||||
@@ -45,22 +71,94 @@ var delegationTestCases = []test.Case{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func TestLookupDelegation(t *testing.T) {
|
func TestLookupDelegation(t *testing.T) {
|
||||||
zone, err := Parse(strings.NewReader(dbMiekNLDelegation), testzone, "stdin")
|
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")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expect no error when reading zone, got %q", err)
|
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}}}
|
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{origin: zone}, Names: []string{origin}}}
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
|
|
||||||
for _, tc := range delegationTestCases {
|
for _, tc := range testcases {
|
||||||
m := tc.Msg()
|
m := tc.Msg()
|
||||||
|
|
||||||
rec := dnsrecorder.New(&test.ResponseWriter{})
|
rec := dnsrecorder.New(&test.ResponseWriter{})
|
||||||
_, err := fm.ServeDNS(ctx, rec, m)
|
_, err := fm.ServeDNS(ctx, rec, m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected no error, got %v\n", err)
|
t.Errorf("Expected no error, got %q\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ var entTestCases = []test.Case{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLookupENT(t *testing.T) {
|
func TestLookupEnt(t *testing.T) {
|
||||||
zone, err := Parse(strings.NewReader(dbMiekENTNL), testzone, "stdin")
|
zone, err := Parse(strings.NewReader(dbMiekENTNL), testzone, "stdin")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expect no error when reading zone, got %q", err)
|
t.Fatalf("expect no error when reading zone, got %q", err)
|
||||||
@@ -73,6 +73,7 @@ func TestLookupENT(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fdjfdjkf
|
||||||
const dbMiekENTNL = `; File written on Sat Apr 2 16:43:11 2016
|
const dbMiekENTNL = `; File written on Sat Apr 2 16:43:11 2016
|
||||||
; dnssec_signzone version 9.10.3-P4-Ubuntu
|
; dnssec_signzone version 9.10.3-P4-Ubuntu
|
||||||
miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. (
|
miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. (
|
||||||
|
|||||||
113
middleware/file/example_org.go
Normal file
113
middleware/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== )
|
||||||
|
`
|
||||||
@@ -10,319 +10,3 @@ func BenchmarkParseInsert(b *testing.B) {
|
|||||||
Parse(strings.NewReader(dbMiekENTNL), testzone, "stdin")
|
Parse(strings.NewReader(dbMiekENTNL), testzone, "stdin")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
var testDir = filepath.Join(os.TempDir(), "coredns_testdir")
|
|
||||||
var ErrCustom = errors.New("Custom Error")
|
|
||||||
|
|
||||||
// testFiles is a map with relative paths to test files as keys and file content as values.
|
|
||||||
// The map represents the following structure:
|
|
||||||
// - $TEMP/coredns_testdir/
|
|
||||||
// '-- file1.html
|
|
||||||
// '-- dirwithindex/
|
|
||||||
// '---- index.html
|
|
||||||
// '-- dir/
|
|
||||||
// '---- file2.html
|
|
||||||
// '---- hidden.html
|
|
||||||
var testFiles = map[string]string{
|
|
||||||
"file1.html": "<h1>file1.html</h1>",
|
|
||||||
filepath.Join("dirwithindex", "index.html"): "<h1>dirwithindex/index.html</h1>",
|
|
||||||
filepath.Join("dir", "file2.html"): "<h1>dir/file2.html</h1>",
|
|
||||||
filepath.Join("dir", "hidden.html"): "<h1>dir/hidden.html</h1>",
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestServeHTTP covers positive scenarios when serving files.
|
|
||||||
func TestServeHTTP(t *testing.T) {
|
|
||||||
|
|
||||||
beforeServeHTTPTest(t)
|
|
||||||
defer afterServeHTTPTest(t)
|
|
||||||
|
|
||||||
fileserver := FileServer(http.Dir(testDir), []string{"hidden.html"})
|
|
||||||
|
|
||||||
movedPermanently := "Moved Permanently"
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
url string
|
|
||||||
|
|
||||||
expectedStatus int
|
|
||||||
expectedBodyContent string
|
|
||||||
}{
|
|
||||||
// Test 0 - access without any path
|
|
||||||
{
|
|
||||||
url: "https://foo",
|
|
||||||
expectedStatus: http.StatusNotFound,
|
|
||||||
},
|
|
||||||
// Test 1 - access root (without index.html)
|
|
||||||
{
|
|
||||||
url: "https://foo/",
|
|
||||||
expectedStatus: http.StatusNotFound,
|
|
||||||
},
|
|
||||||
// Test 2 - access existing file
|
|
||||||
{
|
|
||||||
url: "https://foo/file1.html",
|
|
||||||
expectedStatus: http.StatusOK,
|
|
||||||
expectedBodyContent: testFiles["file1.html"],
|
|
||||||
},
|
|
||||||
// Test 3 - access folder with index file with trailing slash
|
|
||||||
{
|
|
||||||
url: "https://foo/dirwithindex/",
|
|
||||||
expectedStatus: http.StatusOK,
|
|
||||||
expectedBodyContent: testFiles[filepath.Join("dirwithindex", "index.html")],
|
|
||||||
},
|
|
||||||
// Test 4 - access folder with index file without trailing slash
|
|
||||||
{
|
|
||||||
url: "https://foo/dirwithindex",
|
|
||||||
expectedStatus: http.StatusMovedPermanently,
|
|
||||||
expectedBodyContent: movedPermanently,
|
|
||||||
},
|
|
||||||
// Test 5 - access folder without index file
|
|
||||||
{
|
|
||||||
url: "https://foo/dir/",
|
|
||||||
expectedStatus: http.StatusNotFound,
|
|
||||||
},
|
|
||||||
// Test 6 - access folder without trailing slash
|
|
||||||
{
|
|
||||||
url: "https://foo/dir",
|
|
||||||
expectedStatus: http.StatusMovedPermanently,
|
|
||||||
expectedBodyContent: movedPermanently,
|
|
||||||
},
|
|
||||||
// Test 6 - access file with trailing slash
|
|
||||||
{
|
|
||||||
url: "https://foo/file1.html/",
|
|
||||||
expectedStatus: http.StatusMovedPermanently,
|
|
||||||
expectedBodyContent: movedPermanently,
|
|
||||||
},
|
|
||||||
// Test 7 - access not existing path
|
|
||||||
{
|
|
||||||
url: "https://foo/not_existing",
|
|
||||||
expectedStatus: http.StatusNotFound,
|
|
||||||
},
|
|
||||||
// Test 8 - access a file, marked as hidden
|
|
||||||
{
|
|
||||||
url: "https://foo/dir/hidden.html",
|
|
||||||
expectedStatus: http.StatusNotFound,
|
|
||||||
},
|
|
||||||
// Test 9 - access a index file directly
|
|
||||||
{
|
|
||||||
url: "https://foo/dirwithindex/index.html",
|
|
||||||
expectedStatus: http.StatusOK,
|
|
||||||
expectedBodyContent: testFiles[filepath.Join("dirwithindex", "index.html")],
|
|
||||||
},
|
|
||||||
// Test 10 - send a request with query params
|
|
||||||
{
|
|
||||||
url: "https://foo/dir?param1=val",
|
|
||||||
expectedStatus: http.StatusMovedPermanently,
|
|
||||||
expectedBodyContent: movedPermanently,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
responseRecorder := httptest.NewRecorder()
|
|
||||||
request, err := http.NewRequest("GET", test.url, strings.NewReader(""))
|
|
||||||
status, err := fileserver.ServeHTTP(responseRecorder, request)
|
|
||||||
|
|
||||||
// check if error matches expectations
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf(getTestPrefix(i)+"Serving file at %s failed. Error was: %v", test.url, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check status code
|
|
||||||
if test.expectedStatus != status {
|
|
||||||
t.Errorf(getTestPrefix(i)+"Expected status %d, found %d", test.expectedStatus, status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check body content
|
|
||||||
if !strings.Contains(responseRecorder.Body.String(), test.expectedBodyContent) {
|
|
||||||
t.Errorf(getTestPrefix(i)+"Expected body to contain %q, found %q", test.expectedBodyContent, responseRecorder.Body.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// beforeServeHTTPTest creates a test directory with the structure, defined in the variable testFiles
|
|
||||||
func beforeServeHTTPTest(t *testing.T) {
|
|
||||||
// make the root test dir
|
|
||||||
err := os.Mkdir(testDir, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
if !os.IsExist(err) {
|
|
||||||
t.Fatalf("Failed to create test dir. Error was: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for relFile, fileContent := range testFiles {
|
|
||||||
absFile := filepath.Join(testDir, relFile)
|
|
||||||
|
|
||||||
// make sure the parent directories exist
|
|
||||||
parentDir := filepath.Dir(absFile)
|
|
||||||
_, err = os.Stat(parentDir)
|
|
||||||
if err != nil {
|
|
||||||
os.MkdirAll(parentDir, os.ModePerm)
|
|
||||||
}
|
|
||||||
|
|
||||||
// now create the test files
|
|
||||||
f, err := os.Create(absFile)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create test file %s. Error was: %v", absFile, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// and fill them with content
|
|
||||||
_, err = f.WriteString(fileContent)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to write to %s. Error was: %v", absFile, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// afterServeHTTPTest removes the test dir and all its content
|
|
||||||
func afterServeHTTPTest(t *testing.T) {
|
|
||||||
// cleans up everything under the test dir. No need to clean the individual files.
|
|
||||||
err := os.RemoveAll(testDir)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to clean up test dir %s. Error was: %v", testDir, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// failingFS implements the http.FileSystem interface. The Open method always returns the error, assigned to err
|
|
||||||
type failingFS struct {
|
|
||||||
err error // the error to return when Open is called
|
|
||||||
fileImpl http.File // inject the file implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open returns the assigned failingFile and error
|
|
||||||
func (f failingFS) Open(path string) (http.File, error) {
|
|
||||||
return f.fileImpl, f.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// failingFile implements http.File but returns a predefined error on every Stat() method call.
|
|
||||||
type failingFile struct {
|
|
||||||
http.File
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stat returns nil FileInfo and the provided error on every call
|
|
||||||
func (ff failingFile) Stat() (os.FileInfo, error) {
|
|
||||||
return nil, ff.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close is noop and returns no error
|
|
||||||
func (ff failingFile) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestServeHTTPFailingFS tests error cases where the Open function fails with various errors.
|
|
||||||
func TestServeHTTPFailingFS(t *testing.T) {
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
fsErr error
|
|
||||||
expectedStatus int
|
|
||||||
expectedErr error
|
|
||||||
expectedHeaders map[string]string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
fsErr: os.ErrNotExist,
|
|
||||||
expectedStatus: http.StatusNotFound,
|
|
||||||
expectedErr: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fsErr: os.ErrPermission,
|
|
||||||
expectedStatus: http.StatusForbidden,
|
|
||||||
expectedErr: os.ErrPermission,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fsErr: ErrCustom,
|
|
||||||
expectedStatus: http.StatusServiceUnavailable,
|
|
||||||
expectedErr: ErrCustom,
|
|
||||||
expectedHeaders: map[string]string{"Retry-After": "5"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
// initialize a file server with the failing FileSystem
|
|
||||||
fileserver := FileServer(failingFS{err: test.fsErr}, nil)
|
|
||||||
|
|
||||||
// prepare the request and response
|
|
||||||
request, err := http.NewRequest("GET", "https://foo/", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to build request. Error was: %v", err)
|
|
||||||
}
|
|
||||||
responseRecorder := httptest.NewRecorder()
|
|
||||||
|
|
||||||
status, actualErr := fileserver.ServeHTTP(responseRecorder, request)
|
|
||||||
|
|
||||||
// check the status
|
|
||||||
if status != test.expectedStatus {
|
|
||||||
t.Errorf(getTestPrefix(i)+"Expected status %d, found %d", test.expectedStatus, status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check the error
|
|
||||||
if actualErr != test.expectedErr {
|
|
||||||
t.Errorf(getTestPrefix(i)+"Expected err %v, found %v", test.expectedErr, actualErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check the headers - a special case for server under load
|
|
||||||
if test.expectedHeaders != nil && len(test.expectedHeaders) > 0 {
|
|
||||||
for expectedKey, expectedVal := range test.expectedHeaders {
|
|
||||||
actualVal := responseRecorder.Header().Get(expectedKey)
|
|
||||||
if expectedVal != actualVal {
|
|
||||||
t.Errorf(getTestPrefix(i)+"Expected header %s: %s, found %s", expectedKey, expectedVal, actualVal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestServeHTTPFailingStat tests error cases where the initial Open function succeeds, but the Stat method on the opened file fails.
|
|
||||||
func TestServeHTTPFailingStat(t *testing.T) {
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
statErr error
|
|
||||||
expectedStatus int
|
|
||||||
expectedErr error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
statErr: os.ErrNotExist,
|
|
||||||
expectedStatus: http.StatusNotFound,
|
|
||||||
expectedErr: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
statErr: os.ErrPermission,
|
|
||||||
expectedStatus: http.StatusForbidden,
|
|
||||||
expectedErr: os.ErrPermission,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
statErr: ErrCustom,
|
|
||||||
expectedStatus: http.StatusInternalServerError,
|
|
||||||
expectedErr: ErrCustom,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
// initialize a file server. The FileSystem will not fail, but calls to the Stat method of the returned File object will
|
|
||||||
fileserver := FileServer(failingFS{err: nil, fileImpl: failingFile{err: test.statErr}}, nil)
|
|
||||||
|
|
||||||
// prepare the request and response
|
|
||||||
request, err := http.NewRequest("GET", "https://foo/", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to build request. Error was: %v", err)
|
|
||||||
}
|
|
||||||
responseRecorder := httptest.NewRecorder()
|
|
||||||
|
|
||||||
status, actualErr := fileserver.ServeHTTP(responseRecorder, request)
|
|
||||||
|
|
||||||
// check the status
|
|
||||||
if status != test.expectedStatus {
|
|
||||||
t.Errorf(getTestPrefix(i)+"Expected status %d, found %d", test.expectedStatus, status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check the error
|
|
||||||
if actualErr != test.expectedErr {
|
|
||||||
t.Errorf(getTestPrefix(i)+"Expected err %v, found %v", test.expectedErr, actualErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|||||||
@@ -25,165 +25,233 @@ const (
|
|||||||
// Lookup looks up qname and qtype in the zone. When do is true DNSSEC records are included.
|
// 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.
|
// Three sets of records are returned, one for the answer, one for authority and one for the additional section.
|
||||||
func (z *Zone) Lookup(qname string, qtype uint16, do bool) ([]dns.RR, []dns.RR, []dns.RR, Result) {
|
func (z *Zone) Lookup(qname string, qtype uint16, do bool) ([]dns.RR, []dns.RR, []dns.RR, Result) {
|
||||||
if qtype == dns.TypeSOA {
|
|
||||||
if !z.NoReload {
|
|
||||||
z.reloadMu.RLock()
|
|
||||||
}
|
|
||||||
|
|
||||||
r1, r2, r3, res := z.lookupSOA(do)
|
|
||||||
|
|
||||||
if !z.NoReload {
|
|
||||||
z.reloadMu.RUnlock()
|
|
||||||
}
|
|
||||||
return r1, r2, r3, res
|
|
||||||
}
|
|
||||||
if qtype == dns.TypeNS && qname == z.origin {
|
|
||||||
if !z.NoReload {
|
|
||||||
z.reloadMu.RLock()
|
|
||||||
}
|
|
||||||
|
|
||||||
r1, r2, r3, res := z.lookupNS(do)
|
|
||||||
|
|
||||||
if !z.NoReload {
|
|
||||||
z.reloadMu.RUnlock()
|
|
||||||
}
|
|
||||||
return r1, r2, r3, res
|
|
||||||
}
|
|
||||||
|
|
||||||
if !z.NoReload {
|
if !z.NoReload {
|
||||||
z.reloadMu.RLock()
|
z.reloadMu.RLock()
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
elem, res := z.Tree.Search(qname, qtype)
|
if !z.NoReload {
|
||||||
if !z.NoReload {
|
z.reloadMu.RUnlock()
|
||||||
z.reloadMu.RUnlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if elem == nil {
|
|
||||||
if res == tree.EmptyNonTerminal {
|
|
||||||
return z.emptyNonTerminal(qname, do)
|
|
||||||
}
|
}
|
||||||
return z.nameError(qname, qtype, do)
|
}()
|
||||||
|
|
||||||
|
if qtype == dns.TypeSOA {
|
||||||
|
soa := z.soa(do)
|
||||||
|
return soa, nil, nil, Success
|
||||||
}
|
}
|
||||||
if res == tree.Delegation {
|
if qtype == dns.TypeNS && qname == z.origin {
|
||||||
rrs := elem.Types(dns.TypeNS)
|
nsrrs := z.ns(do)
|
||||||
glue := []dns.RR{}
|
glue := z.Glue(nsrrs)
|
||||||
for _, ns := range rrs {
|
return nsrrs, nil, glue, Success
|
||||||
if dns.IsSubDomain(ns.Header().Name, ns.(*dns.NS).Ns) {
|
}
|
||||||
// Even with Do, this should be unsigned.
|
|
||||||
elem, res := z.Tree.SearchGlue(ns.(*dns.NS).Ns)
|
var (
|
||||||
if res == tree.Found {
|
found, shot bool
|
||||||
glue = append(glue, elem.Types(dns.TypeAAAA)...)
|
parts string
|
||||||
glue = append(glue, elem.Types(dns.TypeA)...)
|
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 and do CNAME processing (DNAME should be added as well)
|
||||||
|
// 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. There can only be one wildcard,
|
||||||
|
// so when we found one, we won't look for another.
|
||||||
|
|
||||||
|
if wildElem == nil {
|
||||||
|
wildcard := replaceWithAsteriskLabel(parts)
|
||||||
|
wildElem, _ = z.Tree.Search(wildcard)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 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)
|
||||||
|
// 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, qname); len(rrs) > 0 {
|
||||||
|
return z.searchCNAME(rrs, qtype, do)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
if do {
|
||||||
|
sigs := elem.Types(dns.TypeRRSIG)
|
||||||
|
sigs = signatureForSubType(sigs, qtype)
|
||||||
|
rrs = append(rrs, sigs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rrs, nil, nil, Success
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Haven't found the original name.
|
||||||
|
|
||||||
|
if wildElem != nil {
|
||||||
|
|
||||||
|
if rrs := wildElem.Types(dns.TypeCNAME, qname); len(rrs) > 0 {
|
||||||
|
return z.searchCNAME(rrs, qtype, do)
|
||||||
|
}
|
||||||
|
|
||||||
|
rrs := wildElem.Types(qtype, qname)
|
||||||
|
|
||||||
|
// NODATA
|
||||||
|
if len(rrs) == 0 {
|
||||||
|
ret := z.soa(do)
|
||||||
|
if do {
|
||||||
|
// Do we need to add closest encloser here as well.
|
||||||
|
// closest encloser
|
||||||
|
// ce, _ := z.ClosestEncloser(qname)
|
||||||
|
// println("CLOSEST ENCLOSER", ce.Name()) // need to add this too.
|
||||||
|
nsec := z.typeFromElem(wildElem, dns.TypeNSEC, do)
|
||||||
|
ret = append(ret, nsec...)
|
||||||
|
}
|
||||||
|
return nil, ret, nil, Success
|
||||||
|
}
|
||||||
|
if do {
|
||||||
|
sigs := wildElem.Types(dns.TypeRRSIG, qname)
|
||||||
|
sigs = signatureForSubType(sigs, qtype)
|
||||||
|
rrs = append(rrs, sigs...)
|
||||||
|
|
||||||
|
}
|
||||||
|
return rrs, nil, 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 the 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, found := z.Tree.Prev(qname)
|
||||||
|
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...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, rrs, glue, Delegation
|
|
||||||
}
|
|
||||||
|
|
||||||
rrs := elem.Types(dns.TypeCNAME, qname)
|
|
||||||
if len(rrs) > 0 { // should only ever be 1 actually; TODO(miek) check for this?
|
|
||||||
return z.lookupCNAME(rrs, qtype, do)
|
|
||||||
}
|
}
|
||||||
|
Out:
|
||||||
rrs = elem.Types(qtype, qname)
|
return nil, ret, nil, rcode
|
||||||
if len(rrs) == 0 {
|
|
||||||
return z.noData(elem, do)
|
|
||||||
}
|
|
||||||
|
|
||||||
if do {
|
|
||||||
sigs := elem.Types(dns.TypeRRSIG, qname)
|
|
||||||
sigs = signatureForSubType(sigs, qtype)
|
|
||||||
rrs = append(rrs, sigs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return rrs, nil, nil, Success
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *Zone) noData(elem *tree.Elem, do bool) ([]dns.RR, []dns.RR, []dns.RR, Result) {
|
// Return type tp from e and add signatures (if they exists) and do is true.
|
||||||
soa, _, _, _ := z.lookupSOA(do)
|
func (z *Zone) typeFromElem(elem *tree.Elem, tp uint16, do bool) []dns.RR {
|
||||||
nsec := z.lookupNSEC(elem, do)
|
rrs := elem.Types(tp)
|
||||||
return nil, append(soa, nsec...), nil, Success
|
|
||||||
}
|
|
||||||
|
|
||||||
func (z *Zone) emptyNonTerminal(qname string, do bool) ([]dns.RR, []dns.RR, []dns.RR, Result) {
|
|
||||||
soa, _, _, _ := z.lookupSOA(do)
|
|
||||||
|
|
||||||
elem := z.Tree.Prev(qname)
|
|
||||||
nsec := z.lookupNSEC(elem, do)
|
|
||||||
return nil, append(soa, nsec...), nil, Success
|
|
||||||
}
|
|
||||||
|
|
||||||
func (z *Zone) nameError(qname string, qtype uint16, do bool) ([]dns.RR, []dns.RR, []dns.RR, Result) {
|
|
||||||
// Is there a wildcard?
|
|
||||||
ce := z.ClosestEncloser(qname, qtype)
|
|
||||||
elem, _ := z.Tree.Search("*."+ce, qtype) // use result here?
|
|
||||||
|
|
||||||
if elem != nil {
|
|
||||||
ret := elem.Types(qtype) // there can only be one of these (or zero)
|
|
||||||
switch {
|
|
||||||
case ret != nil:
|
|
||||||
if do {
|
|
||||||
sigs := elem.Types(dns.TypeRRSIG)
|
|
||||||
sigs = signatureForSubType(sigs, qtype)
|
|
||||||
ret = append(ret, sigs...)
|
|
||||||
}
|
|
||||||
ret = wildcardReplace(qname, ce, ret)
|
|
||||||
return ret, nil, nil, Success
|
|
||||||
case ret == nil:
|
|
||||||
// nodata, nsec from the wildcard - type does not exist
|
|
||||||
// nsec proof that name does not exist
|
|
||||||
// TODO(miek)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// name error
|
|
||||||
ret := []dns.RR{z.Apex.SOA}
|
|
||||||
if do {
|
|
||||||
ret = append(ret, z.Apex.SIGSOA...)
|
|
||||||
ret = append(ret, z.nameErrorProof(qname, qtype)...)
|
|
||||||
}
|
|
||||||
return nil, ret, nil, NameError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (z *Zone) lookupSOA(do bool) ([]dns.RR, []dns.RR, []dns.RR, Result) {
|
|
||||||
if do {
|
|
||||||
ret := append([]dns.RR{z.Apex.SOA}, z.Apex.SIGSOA...)
|
|
||||||
return ret, nil, nil, Success
|
|
||||||
}
|
|
||||||
return []dns.RR{z.Apex.SOA}, nil, nil, Success
|
|
||||||
}
|
|
||||||
|
|
||||||
func (z *Zone) lookupNS(do bool) ([]dns.RR, []dns.RR, []dns.RR, Result) {
|
|
||||||
if do {
|
|
||||||
ret := append(z.Apex.NS, z.Apex.SIGNS...)
|
|
||||||
return ret, nil, nil, Success
|
|
||||||
}
|
|
||||||
return z.Apex.NS, nil, nil, Success
|
|
||||||
}
|
|
||||||
|
|
||||||
// lookupNSEC looks up nsec and sigs.
|
|
||||||
func (z *Zone) lookupNSEC(elem *tree.Elem, do bool) []dns.RR {
|
|
||||||
if !do {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
nsec := elem.Types(dns.TypeNSEC)
|
|
||||||
if do {
|
if do {
|
||||||
sigs := elem.Types(dns.TypeRRSIG)
|
sigs := elem.Types(dns.TypeRRSIG)
|
||||||
sigs = signatureForSubType(sigs, dns.TypeNSEC)
|
sigs = signatureForSubType(sigs, tp)
|
||||||
if len(sigs) > 0 {
|
if len(sigs) > 0 {
|
||||||
nsec = append(nsec, sigs...)
|
rrs = append(rrs, sigs...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nsec
|
return rrs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *Zone) lookupCNAME(rrs []dns.RR, qtype uint16, do bool) ([]dns.RR, []dns.RR, []dns.RR, Result) {
|
func (z *Zone) soa(do bool) []dns.RR {
|
||||||
elem, _ := z.Tree.Search(rrs[0].(*dns.CNAME).Target, qtype)
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *Zone) searchCNAME(rrs []dns.RR, qtype uint16, do bool) ([]dns.RR, []dns.RR, []dns.RR, Result) {
|
||||||
|
elem, _ := z.Tree.Search(rrs[0].(*dns.CNAME).Target)
|
||||||
if elem == nil {
|
if elem == nil {
|
||||||
return rrs, nil, nil, Success
|
return rrs, nil, nil, Success
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RECURSIVE SEARCH, up to 8 deep. Also: tests.
|
||||||
targets := cnameForType(elem.All(), qtype)
|
targets := cnameForType(elem.All(), qtype)
|
||||||
if do {
|
if do {
|
||||||
sigs := elem.Types(dns.TypeRRSIG)
|
sigs := elem.Types(dns.TypeRRSIG)
|
||||||
@@ -205,8 +273,7 @@ func cnameForType(targets []dns.RR, origQtype uint16) []dns.RR {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// signatureForSubType range through the signature and return the correct
|
// signatureForSubType range through the signature and return the correct ones for the subtype.
|
||||||
// ones for the subtype.
|
|
||||||
func signatureForSubType(rrs []dns.RR, subtype uint16) []dns.RR {
|
func signatureForSubType(rrs []dns.RR, subtype uint16) []dns.RR {
|
||||||
sigs := []dns.RR{}
|
sigs := []dns.RR{}
|
||||||
for _, sig := range rrs {
|
for _, sig := range rrs {
|
||||||
@@ -219,13 +286,29 @@ func signatureForSubType(rrs []dns.RR, subtype uint16) []dns.RR {
|
|||||||
return sigs
|
return sigs
|
||||||
}
|
}
|
||||||
|
|
||||||
// wildcardReplace replaces the ownername with the original query name.
|
// Glue returns any potential glue records for nsrrs.
|
||||||
func wildcardReplace(qname, ce string, rrs []dns.RR) []dns.RR {
|
func (z *Zone) Glue(nsrrs []dns.RR) []dns.RR {
|
||||||
// need to copy here, otherwise we change in zone stuff
|
glue := []dns.RR{}
|
||||||
ret := make([]dns.RR, len(rrs))
|
for _, ns := range nsrrs {
|
||||||
for i, r := range rrs {
|
if dns.IsSubDomain(ns.Header().Name, ns.(*dns.NS).Ns) {
|
||||||
ret[i] = dns.Copy(r)
|
glue = append(glue, z.searchGlue(ns.(*dns.NS).Ns)...)
|
||||||
ret[i].Header().Name = qname
|
}
|
||||||
}
|
}
|
||||||
return ret
|
return glue
|
||||||
|
}
|
||||||
|
|
||||||
|
// searchGlue looks up A and AAAA for name.
|
||||||
|
func (z *Zone) searchGlue(name string) []dns.RR {
|
||||||
|
glue := []dns.RR{}
|
||||||
|
|
||||||
|
// A
|
||||||
|
if elem, found := z.Tree.Search(name); found {
|
||||||
|
glue = append(glue, elem.Types(dns.TypeA)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AAAA
|
||||||
|
if elem, found := z.Tree.Search(name); found {
|
||||||
|
glue = append(glue, elem.Types(dns.TypeAAAA)...)
|
||||||
|
}
|
||||||
|
return glue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,13 +52,10 @@ func (e *Elem) Name() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsWildcard returns true if this name starts with a wildcard label (*.)
|
// Empty returns true is e does not contain any RRs, i.e. is an
|
||||||
func (e *Elem) IsWildcard() bool {
|
// empty-non-terminal.
|
||||||
n := e.Name()
|
func (e *Elem) Empty() bool {
|
||||||
if len(n) < 2 {
|
return len(e.m) == 0
|
||||||
return false
|
|
||||||
}
|
|
||||||
return n[0] == '*' && n[1] == '.'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert inserts rr into e. If rr is equal to existing rrs this is a noop.
|
// Insert inserts rr into e. If rr is equal to existing rrs this is a noop.
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
// The function orders names in DNSSEC canonical order: RFC 4034s section-6.1
|
// 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
|
// See http://bert-hubert.blogspot.co.uk/2015/10/how-to-do-fast-canonical-ordering-of.html
|
||||||
// for a blog article on this implementation.
|
// 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!
|
// The values of a and b are *not* lowercased before the comparison!
|
||||||
func less(a, b string) int {
|
func less(a, b string) int {
|
||||||
@@ -24,6 +24,7 @@ func less(a, b string) int {
|
|||||||
if oka && okb {
|
if oka && okb {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// sadly this []byte will allocate... TODO(miek): check if this is needed
|
// sadly this []byte will allocate... TODO(miek): check if this is needed
|
||||||
// for a name, otherwise compare the strings.
|
// for a name, otherwise compare the strings.
|
||||||
ab := []byte(a[ai:aj])
|
ab := []byte(a[ai:aj])
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ Tests:
|
|||||||
sort.Sort(set(test.in))
|
sort.Sort(set(test.in))
|
||||||
for i := 0; i < len(test.in); i++ {
|
for i := 0; i < len(test.in); i++ {
|
||||||
if test.in[i] != test.out[i] {
|
if test.in[i] != test.out[i] {
|
||||||
t.Errorf("test %d: expected %s, got %s\n", j, test.out[i], test.in[i])
|
t.Errorf("Test %d: expected %s, got %s\n", j, test.out[i], test.in[i])
|
||||||
n := ""
|
n := ""
|
||||||
for k, in := range test.in {
|
for k, in := range test.in {
|
||||||
if k+1 == len(test.in) {
|
if k+1 == len(test.in) {
|
||||||
|
|||||||
58
middleware/file/tree/print.go
Normal file
58
middleware/file/tree/print.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
func NewQueue() queue {
|
||||||
|
q := queue([]*Node{})
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queue) Push(n *Node) {
|
||||||
|
*q = append(*q, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queue) Pop() *Node {
|
||||||
|
n := (*q)[0]
|
||||||
|
*q = (*q)[1:]
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queue) Empty() bool {
|
||||||
|
return len(*q) == 0
|
||||||
|
}
|
||||||
@@ -13,9 +13,6 @@
|
|||||||
// Heavily modified by Miek Gieben for use in DNS zones.
|
// Heavily modified by Miek Gieben for use in DNS zones.
|
||||||
package tree
|
package tree
|
||||||
|
|
||||||
// TODO(miek): locking? lockfree would be nice. Will probably go for fine grained locking on the name level.
|
|
||||||
// TODO(miek): fix docs
|
|
||||||
|
|
||||||
import "github.com/miekg/dns"
|
import "github.com/miekg/dns"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -23,17 +20,6 @@ const (
|
|||||||
bu23
|
bu23
|
||||||
)
|
)
|
||||||
|
|
||||||
// Result is a result of a Search.
|
|
||||||
type Result int
|
|
||||||
|
|
||||||
// Various constants that indicated the type a resource returned.
|
|
||||||
const (
|
|
||||||
Found Result = iota
|
|
||||||
NameError
|
|
||||||
EmptyNonTerminal
|
|
||||||
Delegation
|
|
||||||
)
|
|
||||||
|
|
||||||
// Operation mode of the LLRB tree.
|
// Operation mode of the LLRB tree.
|
||||||
const mode = bu23
|
const mode = bu23
|
||||||
|
|
||||||
@@ -151,77 +137,32 @@ func (t *Tree) Len() int {
|
|||||||
return t.Count
|
return t.Count
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search returns the first match of qname/qtype in the Tree.
|
// Search returns the first match of qname in the Tree.
|
||||||
func (t *Tree) Search(qname string, qtype uint16) (*Elem, Result) {
|
func (t *Tree) Search(qname string) (*Elem, bool) {
|
||||||
if t.Root == nil {
|
if t.Root == nil {
|
||||||
return nil, NameError
|
return nil, false
|
||||||
}
|
}
|
||||||
n, res := t.Root.search(qname, qtype, false)
|
n, res := t.Root.search(qname)
|
||||||
if n == nil {
|
if n == nil {
|
||||||
return nil, res
|
return nil, res
|
||||||
}
|
}
|
||||||
return n.Elem, res
|
return n.Elem, res
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchGlue returns the first match of qname/(A/AAAA) in the Tree.
|
// search searches the tree for qname and type.
|
||||||
func (t *Tree) SearchGlue(qname string) (*Elem, Result) {
|
func (n *Node) search(qname string) (*Node, bool) {
|
||||||
// TODO(miek): shouldn't need this, because when we *find* the delegation, we
|
|
||||||
// know for sure that any glue is under it. Should change the return values
|
|
||||||
// to return the node, so we can resume from those.
|
|
||||||
if t.Root == nil {
|
|
||||||
return nil, NameError
|
|
||||||
}
|
|
||||||
n, res := t.Root.search(qname, dns.TypeA, true)
|
|
||||||
if n == nil {
|
|
||||||
return nil, res
|
|
||||||
}
|
|
||||||
return n.Elem, res
|
|
||||||
}
|
|
||||||
|
|
||||||
// search searches the tree for qname and type. If glue is true the search *does* not
|
|
||||||
// stop when hitting NS records, but descends in search of glue. The qtype for this
|
|
||||||
// kind of search can only be AAAA or A.
|
|
||||||
func (n *Node) search(qname string, qtype uint16, glue bool) (*Node, Result) {
|
|
||||||
old := n
|
|
||||||
|
|
||||||
var wild *Node
|
|
||||||
|
|
||||||
for n != nil {
|
for n != nil {
|
||||||
|
|
||||||
// Is this a wildcard that applies to us
|
|
||||||
if n.Elem.IsWildcard() {
|
|
||||||
if dns.IsSubDomain(n.Elem.Name()[2:], qname) {
|
|
||||||
wild = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch c := Less(n.Elem, qname); {
|
switch c := Less(n.Elem, qname); {
|
||||||
case c == 0:
|
case c == 0:
|
||||||
return n, Found
|
return n, true
|
||||||
case c < 0:
|
case c < 0:
|
||||||
old = n
|
|
||||||
n = n.Left
|
n = n.Left
|
||||||
default:
|
default:
|
||||||
if !glue && n.Elem.Types(dns.TypeNS) != nil {
|
|
||||||
return n, Delegation
|
|
||||||
|
|
||||||
}
|
|
||||||
old = n
|
|
||||||
n = n.Right
|
n = n.Right
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have seen a wildcard "on-the-way-to-here", we should return this wildcard
|
return n, false
|
||||||
// instead. This is to be able to have a more specific RR defined *under* the wildcard.
|
|
||||||
if wild != nil {
|
|
||||||
return wild, Found
|
|
||||||
}
|
|
||||||
|
|
||||||
if dns.CountLabel(qname) < dns.CountLabel(old.Elem.Name()) {
|
|
||||||
return n, EmptyNonTerminal
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, NameError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert inserts rr into the Tree at the first match found
|
// Insert inserts rr into the Tree at the first match found
|
||||||
@@ -233,6 +174,7 @@ func (t *Tree) Insert(rr dns.RR) {
|
|||||||
t.Root.Color = black
|
t.Root.Color = black
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// insert inserts rr in to the tree.
|
||||||
func (n *Node) insert(rr dns.RR) (root *Node, d int) {
|
func (n *Node) insert(rr dns.RR) (root *Node, d int) {
|
||||||
if n == nil {
|
if n == nil {
|
||||||
return &Node{Elem: newElem(rr)}, 1
|
return &Node{Elem: newElem(rr)}, 1
|
||||||
@@ -339,21 +281,21 @@ func (t *Tree) Delete(rr dns.RR) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
el, _ := t.Search(rr.Header().Name, rr.Header().Rrtype)
|
el, _ := t.Search(rr.Header().Name)
|
||||||
if el == nil {
|
if el == nil {
|
||||||
t.DeleteNode(rr)
|
t.deleteNode(rr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Delete from this element.
|
// Delete from this element.
|
||||||
empty := el.Delete(rr)
|
empty := el.Delete(rr)
|
||||||
if empty {
|
if empty {
|
||||||
t.DeleteNode(rr)
|
t.deleteNode(rr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteNode deletes the node that matches rr according to Less().
|
// DeleteNode deletes the node that matches rr according to Less().
|
||||||
func (t *Tree) DeleteNode(rr dns.RR) {
|
func (t *Tree) deleteNode(rr dns.RR) {
|
||||||
if t.Root == nil {
|
if t.Root == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -427,15 +369,16 @@ func (n *Node) max() *Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prev returns the greatest value equal to or less than the qname according to Less().
|
// Prev returns the greatest value equal to or less than the qname according to Less().
|
||||||
func (t *Tree) Prev(qname string) *Elem {
|
func (t *Tree) Prev(qname string) (*Elem, bool) {
|
||||||
if t.Root == nil {
|
if t.Root == nil {
|
||||||
return nil
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
n := t.Root.floor(qname)
|
n := t.Root.floor(qname)
|
||||||
if n == nil {
|
if n == nil {
|
||||||
return nil
|
return nil, false
|
||||||
}
|
}
|
||||||
return n.Elem
|
return n.Elem, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Node) floor(qname string) *Node {
|
func (n *Node) floor(qname string) *Node {
|
||||||
@@ -445,7 +388,7 @@ func (n *Node) floor(qname string) *Node {
|
|||||||
switch c := Less(n.Elem, qname); {
|
switch c := Less(n.Elem, qname); {
|
||||||
case c == 0:
|
case c == 0:
|
||||||
return n
|
return n
|
||||||
case c < 0:
|
case c <= 0:
|
||||||
return n.Left.floor(qname)
|
return n.Left.floor(qname)
|
||||||
default:
|
default:
|
||||||
if r := n.Right.floor(qname); r != nil {
|
if r := n.Right.floor(qname); r != nil {
|
||||||
@@ -456,15 +399,15 @@ func (n *Node) floor(qname string) *Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Next returns the smallest value equal to or greater than the qname according to Less().
|
// Next returns the smallest value equal to or greater than the qname according to Less().
|
||||||
func (t *Tree) Next(qname string) *Elem {
|
func (t *Tree) Next(qname string) (*Elem, bool) {
|
||||||
if t.Root == nil {
|
if t.Root == nil {
|
||||||
return nil
|
return nil, false
|
||||||
}
|
}
|
||||||
n := t.Root.ceil(qname)
|
n := t.Root.ceil(qname)
|
||||||
if n == nil {
|
if n == nil {
|
||||||
return nil
|
return nil, false
|
||||||
}
|
}
|
||||||
return n.Elem
|
return n.Elem, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Node) ceil(qname string) *Node {
|
func (n *Node) ceil(qname string) *Node {
|
||||||
|
|||||||
13
middleware/file/wildcard.go
Normal file
13
middleware/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:]
|
||||||
|
}
|
||||||
@@ -51,6 +51,7 @@ var wildcardTestCases = []test.Case{
|
|||||||
{
|
{
|
||||||
Qname: "wild.dnssex.nl.", Qtype: dns.TypeSRV, Do: true,
|
Qname: "wild.dnssex.nl.", Qtype: dns.TypeSRV, Do: true,
|
||||||
Ns: []dns.RR{
|
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.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. os6INm6q2eXknD5z8TpfbK00uxVbQefMvHcR/RNX/kh0xXvzAaaDOV+Ge/Ko+2dXnKP+J1LYG9ffXNpdbaQy5ygzH5F041GJst4566GdG/jt7Z7vLHYxEBTpZfxo+PLsXQXH3VTemZyuWyDfqJzafXJVH1F0nDrcXmMlR6jlBHA=`),
|
test.RRSIG(`*.dnssex.nl. 14400 IN RRSIG NSEC 8 2 14400 20160428190224 20160329190224 14460 dnssex.nl. os6INm6q2eXknD5z8TpfbK00uxVbQefMvHcR/RNX/kh0xXvzAaaDOV+Ge/Ko+2dXnKP+J1LYG9ffXNpdbaQy5ygzH5F041GJst4566GdG/jt7Z7vLHYxEBTpZfxo+PLsXQXH3VTemZyuWyDfqJzafXJVH1F0nDrcXmMlR6jlBHA=`),
|
||||||
test.RRSIG(`dnssex.nl. 1800 IN RRSIG SOA 8 2 1800 20160428190224 20160329190224 14460 dnssex.nl. CA/Y3m9hCOiKC/8ieSOv8SeP964BUdG/8MC3WtKljUosK9Z9bBGrVizDjjqgq++lyH8BZJcTaabAsERs4xj5PRtcxicwQXZACX5VYjXHQeZmCyytFU5wq2gcXSmvUH86zZzftx3RGPvn1aOoTlcvoC3iF8fYUCpROlUS0YR8Cdw=`),
|
test.RRSIG(`dnssex.nl. 1800 IN RRSIG SOA 8 2 1800 20160428190224 20160329190224 14460 dnssex.nl. CA/Y3m9hCOiKC/8ieSOv8SeP964BUdG/8MC3WtKljUosK9Z9bBGrVizDjjqgq++lyH8BZJcTaabAsERs4xj5PRtcxicwQXZACX5VYjXHQeZmCyytFU5wq2gcXSmvUH86zZzftx3RGPvn1aOoTlcvoC3iF8fYUCpROlUS0YR8Cdw=`),
|
||||||
@@ -63,7 +64,7 @@ var wildcardTestCases = []test.Case{
|
|||||||
func TestLookupWildcard(t *testing.T) {
|
func TestLookupWildcard(t *testing.T) {
|
||||||
zone, err := Parse(strings.NewReader(dbDnssexNLSigned), testzone1, "stdin")
|
zone, err := Parse(strings.NewReader(dbDnssexNLSigned), testzone1, "stdin")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expect no error when reading zone, got %q", err)
|
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}}}
|
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone1: zone}, Names: []string{testzone1}}}
|
||||||
@@ -75,7 +76,7 @@ func TestLookupWildcard(t *testing.T) {
|
|||||||
rec := dnsrecorder.New(&test.ResponseWriter{})
|
rec := dnsrecorder.New(&test.ResponseWriter{})
|
||||||
_, err := fm.ServeDNS(ctx, rec, m)
|
_, err := fm.ServeDNS(ctx, rec, m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected no error, got %v\n", err)
|
t.Errorf("Expected no error, got %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +132,7 @@ var wildcardDoubleTestCases = []test.Case{
|
|||||||
func TestLookupDoubleWildcard(t *testing.T) {
|
func TestLookupDoubleWildcard(t *testing.T) {
|
||||||
zone, err := Parse(strings.NewReader(exampleOrg), "example.org.", "stdin")
|
zone, err := Parse(strings.NewReader(exampleOrg), "example.org.", "stdin")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expect no error when reading zone, got %q", err)
|
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."}}}
|
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{"example.org.": zone}, Names: []string{"example.org."}}}
|
||||||
@@ -143,7 +144,7 @@ func TestLookupDoubleWildcard(t *testing.T) {
|
|||||||
rec := dnsrecorder.New(&test.ResponseWriter{})
|
rec := dnsrecorder.New(&test.ResponseWriter{})
|
||||||
_, err := fm.ServeDNS(ctx, rec, m)
|
_, err := fm.ServeDNS(ctx, rec, m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected no error, got %v\n", err)
|
t.Errorf("Expected no error, got %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,6 +169,23 @@ func TestLookupDoubleWildcard(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const exampleOrg = `; example.org test file
|
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 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 b.iana-servers.net.
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ import (
|
|||||||
|
|
||||||
// Zone defines a structure that contains all data related to a DNS zone.
|
// Zone defines a structure that contains all data related to a DNS zone.
|
||||||
type Zone struct {
|
type Zone struct {
|
||||||
origin string
|
origin string
|
||||||
file string
|
origLen int
|
||||||
|
file string
|
||||||
*tree.Tree
|
*tree.Tree
|
||||||
Apex Apex
|
Apex Apex
|
||||||
|
|
||||||
@@ -44,12 +45,14 @@ type Apex struct {
|
|||||||
func NewZone(name, file string) *Zone {
|
func NewZone(name, file string) *Zone {
|
||||||
z := &Zone{
|
z := &Zone{
|
||||||
origin: dns.Fqdn(name),
|
origin: dns.Fqdn(name),
|
||||||
|
origLen: dns.CountLabel(dns.Fqdn(name)),
|
||||||
file: path.Clean(file),
|
file: path.Clean(file),
|
||||||
Tree: &tree.Tree{},
|
Tree: &tree.Tree{},
|
||||||
Expired: new(bool),
|
Expired: new(bool),
|
||||||
ReloadShutdown: make(chan bool),
|
ReloadShutdown: make(chan bool),
|
||||||
}
|
}
|
||||||
*z.Expired = false
|
*z.Expired = false
|
||||||
|
|
||||||
return z
|
return z
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +105,7 @@ func (z *Zone) Insert(r dns.RR) error {
|
|||||||
case dns.TypeSRV:
|
case dns.TypeSRV:
|
||||||
r.(*dns.SRV).Target = strings.ToLower(r.(*dns.SRV).Target)
|
r.(*dns.SRV).Target = strings.ToLower(r.(*dns.SRV).Target)
|
||||||
}
|
}
|
||||||
|
|
||||||
z.Tree.Insert(r)
|
z.Tree.Insert(r)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -165,7 +169,7 @@ func (z *Zone) Reload() error {
|
|||||||
select {
|
select {
|
||||||
case event := <-watcher.Events:
|
case event := <-watcher.Events:
|
||||||
// Looks for Write and Create events. Write is obvious, Create is used when
|
// Looks for Write and Create events. Write is obvious, Create is used when
|
||||||
// a file in mv-ed into this place.
|
// a file is mv-ed into this place.
|
||||||
if (event.Op == fsnotify.Write || event.Op == fsnotify.Create) && path.Clean(event.Name) == z.file {
|
if (event.Op == fsnotify.Write || event.Op == fsnotify.Create) && path.Clean(event.Name) == z.file {
|
||||||
|
|
||||||
reader, err := os.Open(z.file)
|
reader, err := os.Open(z.file)
|
||||||
@@ -196,3 +200,33 @@ func (z *Zone) Reload() error {
|
|||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,30 @@
|
|||||||
package file
|
package file
|
||||||
|
|
||||||
// TODO tests here.
|
import "testing"
|
||||||
// see secondary_test.go for some infrastructure stuff.
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -88,6 +88,9 @@ func NSEC(rr string) *dns.NSEC { r, _ := dns.NewRR(rr); return r.(*dns.NSEC) }
|
|||||||
// DNSKEY returns a DNSKEY record from rr. It panics on errors.
|
// DNSKEY returns a DNSKEY record from rr. It panics on errors.
|
||||||
func DNSKEY(rr string) *dns.DNSKEY { r, _ := dns.NewRR(rr); return r.(*dns.DNSKEY) }
|
func DNSKEY(rr string) *dns.DNSKEY { r, _ := dns.NewRR(rr); return r.(*dns.DNSKEY) }
|
||||||
|
|
||||||
|
// DS returns a DS record from rr. It panics on errors.
|
||||||
|
func DS(rr string) *dns.DS { r, _ := dns.NewRR(rr); return r.(*dns.DS) }
|
||||||
|
|
||||||
// OPT returns an OPT record with UDP buffer size set to bufsize and the DO bit set to do.
|
// OPT returns an OPT record with UDP buffer size set to bufsize and the DO bit set to do.
|
||||||
func OPT(bufsize int, do bool) *dns.OPT {
|
func OPT(bufsize int, do bool) *dns.OPT {
|
||||||
o := new(dns.OPT)
|
o := new(dns.OPT)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func TestLookupWildcard(t *testing.T) {
|
|||||||
p := proxy.New([]string{udp})
|
p := proxy.New([]string{udp})
|
||||||
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
|
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
|
||||||
|
|
||||||
for _, lookup := range []string{"w.example.org.", "a.w.example.org.", "a.a.w.example.org."} {
|
for _, lookup := range []string{"a.w.example.org.", "a.a.w.example.org."} {
|
||||||
resp, err := p.Lookup(state, lookup, dns.TypeTXT)
|
resp, err := p.Lookup(state, lookup, dns.TypeTXT)
|
||||||
if err != nil || resp == nil {
|
if err != nil || resp == nil {
|
||||||
t.Fatalf("Expected to receive reply, but didn't for %s", lookup)
|
t.Fatalf("Expected to receive reply, but didn't for %s", lookup)
|
||||||
|
|||||||
Reference in New Issue
Block a user