aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--middleware/file/closest.go62
-rw-r--r--middleware/file/closest_test.go14
-rw-r--r--middleware/file/delegation_test.go108
-rw-r--r--middleware/file/ent_test.go3
-rw-r--r--middleware/file/example_org.go113
-rw-r--r--middleware/file/file_test.go316
-rw-r--r--middleware/file/lookup.go321
-rw-r--r--middleware/file/tree/elem.go11
-rw-r--r--middleware/file/tree/less.go3
-rw-r--r--middleware/file/tree/less_test.go2
-rw-r--r--middleware/file/tree/print.go58
-rw-r--r--middleware/file/tree/tree.go103
-rw-r--r--middleware/file/wildcard.go13
-rw-r--r--middleware/file/wildcard_test.go26
-rw-r--r--middleware/file/zone.go40
-rw-r--r--middleware/file/zone_test.go30
-rw-r--r--middleware/test/helpers.go3
-rw-r--r--test/wildcard_test.go2
18 files changed, 631 insertions, 597 deletions
diff --git a/middleware/file/closest.go b/middleware/file/closest.go
index 741327fde..2ee14e6d8 100644
--- a/middleware/file/closest.go
+++ b/middleware/file/closest.go
@@ -1,66 +1,24 @@
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.
-func (z *Zone) ClosestEncloser(qname string, qtype uint16) string {
- // tree/tree.go does not store a parent *Node pointer, so we can't
- // just follow up the tree. TODO(miek): fix.
+func (z *Zone) ClosestEncloser(qname string) (*tree.Elem, bool) {
+
offset, end := dns.NextLabel(qname, 0)
for !end {
- elem, _ := z.Tree.Search(qname, qtype)
+ elem, _ := z.Tree.Search(qname)
if elem != nil {
- return elem.Name()
+ return elem, true
}
qname = qname[offset:]
offset, end = dns.NextLabel(qname, offset)
}
- return z.Apex.SOA.Header().Name
-}
-
-// 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...)
+ return z.Tree.Search(z.origin)
}
diff --git a/middleware/file/closest_test.go b/middleware/file/closest_test.go
index 91b65f231..591577430 100644
--- a/middleware/file/closest_test.go
+++ b/middleware/file/closest_test.go
@@ -3,8 +3,6 @@ package file
import (
"strings"
"testing"
-
- "github.com/miekg/dns"
)
func TestClosestEncloser(t *testing.T) {
@@ -26,9 +24,15 @@ func TestClosestEncloser(t *testing.T) {
}
for _, tc := range tests {
- ce := z.ClosestEncloser(tc.in, dns.TypeA)
- if ce != tc.out {
- t.Errorf("expected ce to be %s for %s, got %s", tc.out, tc.in, ce)
+ 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())
}
}
}
diff --git a/middleware/file/delegation_test.go b/middleware/file/delegation_test.go
index e9a20eb25..63ca1d264 100644
--- a/middleware/file/delegation_test.go
+++ b/middleware/file/delegation_test.go
@@ -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 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,
@@ -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) {
- 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 {
- 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()
- for _, tc := range delegationTestCases {
+ 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 %v\n", err)
+ t.Errorf("Expected no error, got %q\n", err)
return
}
diff --git a/middleware/file/ent_test.go b/middleware/file/ent_test.go
index 324c6cfb6..4ce1403bf 100644
--- a/middleware/file/ent_test.go
+++ b/middleware/file/ent_test.go
@@ -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")
if err != nil {
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
; dnssec_signzone version 9.10.3-P4-Ubuntu
miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. (
diff --git a/middleware/file/example_org.go b/middleware/file/example_org.go
new file mode 100644
index 000000000..eba18e0e4
--- /dev/null
+++ b/middleware/file/example_org.go
@@ -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== )
+`
diff --git a/middleware/file/file_test.go b/middleware/file/file_test.go
index 880dd841c..768817900 100644
--- a/middleware/file/file_test.go
+++ b/middleware/file/file_test.go
@@ -10,319 +10,3 @@ func BenchmarkParseInsert(b *testing.B) {
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)
- }
- }
-}
-*/
diff --git a/middleware/file/lookup.go b/middleware/file/lookup.go
index 970d593b6..16325d3e9 100644
--- a/middleware/file/lookup.go
+++ b/middleware/file/lookup.go
@@ -25,165 +25,233 @@ const (
// 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(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.RLock()
+ }
+ defer func() {
if !z.NoReload {
z.reloadMu.RUnlock()
}
- return r1, r2, r3, res
+ }()
+
+ if qtype == dns.TypeSOA {
+ soa := z.soa(do)
+ return soa, nil, nil, Success
}
if qtype == dns.TypeNS && qname == z.origin {
- if !z.NoReload {
- z.reloadMu.RLock()
+ nsrrs := z.ns(do)
+ glue := z.Glue(nsrrs)
+ 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 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
}
- r1, r2, r3, res := z.lookupNS(do)
+ 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 !z.NoReload {
- z.reloadMu.RUnlock()
+ 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
}
- return r1, r2, r3, res
- }
- if !z.NoReload {
- z.reloadMu.RLock()
+ // 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++
}
- elem, res := z.Tree.Search(qname, qtype)
- if !z.NoReload {
- z.reloadMu.RUnlock()
+ // What does found and !shot mean - do we ever hit it?
+ if found && !shot {
+ return nil, nil, nil, ServerFailure
}
- if elem == nil {
- if res == tree.EmptyNonTerminal {
- return z.emptyNonTerminal(qname, do)
- }
- return z.nameError(qname, qtype, do)
- }
- if res == tree.Delegation {
- rrs := elem.Types(dns.TypeNS)
- glue := []dns.RR{}
- for _, ns := range rrs {
- 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)
- if res == tree.Found {
- glue = append(glue, elem.Types(dns.TypeAAAA)...)
- glue = append(glue, elem.Types(dns.TypeA)...)
- }
+ // 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
}
- 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)
- }
+ if do {
+ sigs := elem.Types(dns.TypeRRSIG)
+ sigs = signatureForSubType(sigs, qtype)
+ rrs = append(rrs, sigs...)
+ }
- rrs = elem.Types(qtype, qname)
- if len(rrs) == 0 {
- return z.noData(elem, do)
- }
+ return rrs, nil, nil, Success
- 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) {
- soa, _, _, _ := z.lookupSOA(do)
- nsec := z.lookupNSEC(elem, do)
- return nil, append(soa, nsec...), nil, Success
-}
+ // Haven't found the original name.
-func (z *Zone) emptyNonTerminal(qname string, do bool) ([]dns.RR, []dns.RR, []dns.RR, Result) {
- soa, _, _, _ := z.lookupSOA(do)
+ if wildElem != nil {
- elem := z.Tree.Prev(qname)
- nsec := z.lookupNSEC(elem, do)
- return nil, append(soa, nsec...), nil, Success
-}
+ if rrs := wildElem.Types(dns.TypeCNAME, qname); len(rrs) > 0 {
+ return z.searchCNAME(rrs, qtype, do)
+ }
-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?
+ rrs := wildElem.Types(qtype, qname)
- if elem != nil {
- ret := elem.Types(qtype) // there can only be one of these (or zero)
- switch {
- case ret != nil:
+ // NODATA
+ if len(rrs) == 0 {
+ ret := z.soa(do)
if do {
- sigs := elem.Types(dns.TypeRRSIG)
- sigs = signatureForSubType(sigs, qtype)
- ret = append(ret, sigs...)
+ // 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...)
}
- 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)
+ 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
}
- // name error
- ret := []dns.RR{z.Apex.SOA}
+ 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 {
- ret = append(ret, z.Apex.SIGSOA...)
- ret = append(ret, z.nameErrorProof(qname, qtype)...)
+ 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, ret, nil, NameError
+Out:
+ return nil, ret, nil, rcode
}
-func (z *Zone) lookupSOA(do bool) ([]dns.RR, []dns.RR, []dns.RR, Result) {
+// 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 {
- ret := append([]dns.RR{z.Apex.SOA}, z.Apex.SIGSOA...)
- return ret, nil, nil, Success
+ sigs := elem.Types(dns.TypeRRSIG)
+ sigs = signatureForSubType(sigs, tp)
+ if len(sigs) > 0 {
+ rrs = append(rrs, sigs...)
+ }
}
- return []dns.RR{z.Apex.SOA}, nil, nil, Success
+ return rrs
}
-func (z *Zone) lookupNS(do bool) ([]dns.RR, []dns.RR, []dns.RR, Result) {
+func (z *Zone) soa(do bool) []dns.RR {
if do {
- ret := append(z.Apex.NS, z.Apex.SIGNS...)
- return ret, nil, nil, Success
+ ret := append([]dns.RR{z.Apex.SOA}, z.Apex.SIGSOA...)
+ return ret
}
- return z.Apex.NS, nil, nil, Success
+ return []dns.RR{z.Apex.SOA}
}
-// 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)
+func (z *Zone) ns(do bool) []dns.RR {
if do {
- sigs := elem.Types(dns.TypeRRSIG)
- sigs = signatureForSubType(sigs, dns.TypeNSEC)
- if len(sigs) > 0 {
- nsec = append(nsec, sigs...)
- }
+ ret := append(z.Apex.NS, z.Apex.SIGNS...)
+ return ret
}
- return nsec
+ return z.Apex.NS
}
-func (z *Zone) lookupCNAME(rrs []dns.RR, qtype uint16, do bool) ([]dns.RR, []dns.RR, []dns.RR, Result) {
- elem, _ := z.Tree.Search(rrs[0].(*dns.CNAME).Target, qtype)
+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 {
return rrs, nil, nil, Success
}
+ // RECURSIVE SEARCH, up to 8 deep. Also: tests.
targets := cnameForType(elem.All(), qtype)
if do {
sigs := elem.Types(dns.TypeRRSIG)
@@ -205,8 +273,7 @@ func cnameForType(targets []dns.RR, origQtype uint16) []dns.RR {
return ret
}
-// signatureForSubType range through the signature and return the correct
-// ones for the subtype.
+// 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 {
@@ -219,13 +286,29 @@ func signatureForSubType(rrs []dns.RR, subtype uint16) []dns.RR {
return sigs
}
-// wildcardReplace replaces the ownername with the original query name.
-func wildcardReplace(qname, ce string, rrs []dns.RR) []dns.RR {
- // need to copy here, otherwise we change in zone stuff
- ret := make([]dns.RR, len(rrs))
- for i, r := range rrs {
- ret[i] = dns.Copy(r)
- ret[i].Header().Name = qname
+// Glue returns any potential glue records for nsrrs.
+func (z *Zone) Glue(nsrrs []dns.RR) []dns.RR {
+ glue := []dns.RR{}
+ for _, ns := range nsrrs {
+ if dns.IsSubDomain(ns.Header().Name, ns.(*dns.NS).Ns) {
+ glue = append(glue, z.searchGlue(ns.(*dns.NS).Ns)...)
+ }
}
- 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
}
diff --git a/middleware/file/tree/elem.go b/middleware/file/tree/elem.go
index 234e2c848..6317cc912 100644
--- a/middleware/file/tree/elem.go
+++ b/middleware/file/tree/elem.go
@@ -52,13 +52,10 @@ func (e *Elem) Name() string {
return ""
}
-// IsWildcard returns true if this name starts with a wildcard label (*.)
-func (e *Elem) IsWildcard() bool {
- n := e.Name()
- if len(n) < 2 {
- return false
- }
- return n[0] == '*' && n[1] == '.'
+// 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.
diff --git a/middleware/file/tree/less.go b/middleware/file/tree/less.go
index 595dc9213..3b8340088 100644
--- a/middleware/file/tree/less.go
+++ b/middleware/file/tree/less.go
@@ -11,7 +11,7 @@ import (
// 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.
+// 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 {
@@ -24,6 +24,7 @@ func less(a, b string) int {
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])
diff --git a/middleware/file/tree/less_test.go b/middleware/file/tree/less_test.go
index 419b75c55..ed021b66f 100644
--- a/middleware/file/tree/less_test.go
+++ b/middleware/file/tree/less_test.go
@@ -65,7 +65,7 @@ Tests:
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])
+ 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) {
diff --git a/middleware/file/tree/print.go b/middleware/file/tree/print.go
new file mode 100644
index 000000000..f098e56c7
--- /dev/null
+++ b/middleware/file/tree/print.go
@@ -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
+}
diff --git a/middleware/file/tree/tree.go b/middleware/file/tree/tree.go
index 668c19734..ed33c09a4 100644
--- a/middleware/file/tree/tree.go
+++ b/middleware/file/tree/tree.go
@@ -13,9 +13,6 @@
// Heavily modified by Miek Gieben for use in DNS zones.
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"
const (
@@ -23,17 +20,6 @@ const (
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.
const mode = bu23
@@ -151,77 +137,32 @@ func (t *Tree) Len() int {
return t.Count
}
-// Search returns the first match of qname/qtype in the Tree.
-func (t *Tree) Search(qname string, qtype uint16) (*Elem, Result) {
- if t.Root == nil {
- return nil, NameError
- }
- n, res := t.Root.search(qname, qtype, false)
- if n == nil {
- return nil, res
- }
- return n.Elem, res
-}
-
-// SearchGlue returns the first match of qname/(A/AAAA) in the Tree.
-func (t *Tree) SearchGlue(qname string) (*Elem, Result) {
- // 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.
+// Search returns the first match of qname in the Tree.
+func (t *Tree) Search(qname string) (*Elem, bool) {
if t.Root == nil {
- return nil, NameError
+ return nil, false
}
- n, res := t.Root.search(qname, dns.TypeA, true)
+ n, res := t.Root.search(qname)
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
-
+// search searches the tree for qname and type.
+func (n *Node) search(qname string) (*Node, bool) {
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); {
case c == 0:
- return n, Found
+ return n, true
case c < 0:
- old = n
n = n.Left
default:
- if !glue && n.Elem.Types(dns.TypeNS) != nil {
- return n, Delegation
-
- }
- old = n
n = n.Right
}
}
- // If we have seen a wildcard "on-the-way-to-here", we should return this wildcard
- // 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
+ return n, false
}
// 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
}
+// 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
@@ -339,21 +281,21 @@ func (t *Tree) Delete(rr dns.RR) {
return
}
- el, _ := t.Search(rr.Header().Name, rr.Header().Rrtype)
+ el, _ := t.Search(rr.Header().Name)
if el == nil {
- t.DeleteNode(rr)
+ t.deleteNode(rr)
return
}
// Delete from this element.
empty := el.Delete(rr)
if empty {
- t.DeleteNode(rr)
+ t.deleteNode(rr)
return
}
}
// 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 {
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().
-func (t *Tree) Prev(qname string) *Elem {
+func (t *Tree) Prev(qname string) (*Elem, bool) {
if t.Root == nil {
- return nil
+ return nil, false
}
+
n := t.Root.floor(qname)
if n == nil {
- return nil
+ return nil, false
}
- return n.Elem
+ return n.Elem, true
}
func (n *Node) floor(qname string) *Node {
@@ -445,7 +388,7 @@ func (n *Node) floor(qname string) *Node {
switch c := Less(n.Elem, qname); {
case c == 0:
return n
- case c < 0:
+ case c <= 0:
return n.Left.floor(qname)
default:
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().
-func (t *Tree) Next(qname string) *Elem {
+func (t *Tree) Next(qname string) (*Elem, bool) {
if t.Root == nil {
- return nil
+ return nil, false
}
n := t.Root.ceil(qname)
if n == nil {
- return nil
+ return nil, false
}
- return n.Elem
+ return n.Elem, true
}
func (n *Node) ceil(qname string) *Node {
diff --git a/middleware/file/wildcard.go b/middleware/file/wildcard.go
new file mode 100644
index 000000000..9526cb53f
--- /dev/null
+++ b/middleware/file/wildcard.go
@@ -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:]
+}
diff --git a/middleware/file/wildcard_test.go b/middleware/file/wildcard_test.go
index 5e7251d88..b3bd5915b 100644
--- a/middleware/file/wildcard_test.go
+++ b/middleware/file/wildcard_test.go
@@ -51,6 +51,7 @@ var wildcardTestCases = []test.Case{
{
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. 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=`),
@@ -63,7 +64,7 @@ var wildcardTestCases = []test.Case{
func TestLookupWildcard(t *testing.T) {
zone, err := Parse(strings.NewReader(dbDnssexNLSigned), testzone1, "stdin")
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}}}
@@ -75,7 +76,7 @@ func TestLookupWildcard(t *testing.T) {
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := fm.ServeDNS(ctx, rec, m)
if err != nil {
- t.Errorf("expected no error, got %v\n", err)
+ t.Errorf("Expected no error, got %v\n", err)
return
}
@@ -131,7 +132,7 @@ var wildcardDoubleTestCases = []test.Case{
func TestLookupDoubleWildcard(t *testing.T) {
zone, err := Parse(strings.NewReader(exampleOrg), "example.org.", "stdin")
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."}}}
@@ -143,7 +144,7 @@ func TestLookupDoubleWildcard(t *testing.T) {
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := fm.ServeDNS(ctx, rec, m)
if err != nil {
- t.Errorf("expected no error, got %v\n", err)
+ t.Errorf("Expected no error, got %v\n", err)
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
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.
diff --git a/middleware/file/zone.go b/middleware/file/zone.go
index ddedc6725..3636c04bd 100644
--- a/middleware/file/zone.go
+++ b/middleware/file/zone.go
@@ -17,8 +17,9 @@ import (
// Zone defines a structure that contains all data related to a DNS zone.
type Zone struct {
- origin string
- file string
+ origin string
+ origLen int
+ file string
*tree.Tree
Apex Apex
@@ -44,12 +45,14 @@ type Apex struct {
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
}
@@ -102,6 +105,7 @@ func (z *Zone) Insert(r dns.RR) error {
case dns.TypeSRV:
r.(*dns.SRV).Target = strings.ToLower(r.(*dns.SRV).Target)
}
+
z.Tree.Insert(r)
return nil
}
@@ -165,7 +169,7 @@ func (z *Zone) Reload() error {
select {
case event := <-watcher.Events:
// 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 {
reader, err := os.Open(z.file)
@@ -196,3 +200,33 @@ func (z *Zone) Reload() error {
}()
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
+}
diff --git a/middleware/file/zone_test.go b/middleware/file/zone_test.go
index e7eaef4b8..c9ff174db 100644
--- a/middleware/file/zone_test.go
+++ b/middleware/file/zone_test.go
@@ -1,4 +1,30 @@
package file
-// TODO tests here.
-// see secondary_test.go for some infrastructure stuff.
+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)
+ }
+ }
+}
diff --git a/middleware/test/helpers.go b/middleware/test/helpers.go
index 66fc3c50c..339385a23 100644
--- a/middleware/test/helpers.go
+++ b/middleware/test/helpers.go
@@ -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.
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.
func OPT(bufsize int, do bool) *dns.OPT {
o := new(dns.OPT)
diff --git a/test/wildcard_test.go b/test/wildcard_test.go
index 3a66dd7ca..57cfdc75a 100644
--- a/test/wildcard_test.go
+++ b/test/wildcard_test.go
@@ -40,7 +40,7 @@ func TestLookupWildcard(t *testing.T) {
p := proxy.New([]string{udp})
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)
if err != nil || resp == nil {
t.Fatalf("Expected to receive reply, but didn't for %s", lookup)