Skip to content

Commit 7e53cce

Browse files
committed
Add RFC4034 domain comparison + NSEC Cover
1 parent 5521648 commit 7e53cce

File tree

4 files changed

+119
-0
lines changed

4 files changed

+119
-0
lines changed

labels.go

+57
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package dns
22

3+
import (
4+
"strings"
5+
)
6+
37
// Holds a bunch of helper functions for dealing with labels.
48

59
// SplitDomainName splits a name string into it's labels.
@@ -186,6 +190,46 @@ func PrevLabel(s string, n int) (i int, start bool) {
186190
return 0, n > 1
187191
}
188192

193+
// compares domains according to the canonical ordering specified in RFC4034
194+
// returns an integer value similar to strcmp
195+
// names have to have equal casing!
196+
// (0 for equal values, -1 if s1 < s2, 1 if s1 > s2)
197+
func Compare(s1, s2 string) int {
198+
s1b := []byte(s1)
199+
s2b := []byte(s2)
200+
201+
doDDD(s1b)
202+
doDDD(s2b)
203+
204+
s1lend := len(s1)
205+
s2lend := len(s2)
206+
207+
for i := 0; ; i++ {
208+
s1lstart, end1 := PrevLabel(s1, i)
209+
s2lstart, end2 := PrevLabel(s2, i)
210+
211+
if end1 && end2 {
212+
return 0
213+
}
214+
215+
s1l := string(s1b[s1lstart:s1lend])
216+
s2l := string(s2b[s2lstart:s2lend])
217+
218+
if !equal(s1l, s2l) {
219+
return strings.Compare(strings.ToLower(s1l), strings.ToLower(s2l))
220+
}
221+
222+
s1lend = s1lstart - 1
223+
s2lend = s2lstart - 1
224+
if s1lend == -1 {
225+
s1lend = 0
226+
}
227+
if s2lend == -1 {
228+
s2lend = 0
229+
}
230+
}
231+
}
232+
189233
// equal compares a and b while ignoring case. It returns true when equal otherwise false.
190234
func equal(a, b string) bool {
191235
// might be lifted into API function.
@@ -210,3 +254,16 @@ func equal(a, b string) bool {
210254
}
211255
return true
212256
}
257+
258+
func doDDD(b []byte) {
259+
lb := len(b)
260+
for i := 0; i < lb; i++ {
261+
if i+3 < lb && b[i] == '\\' && isDigit(b[i+1]) && isDigit(b[i+2]) && isDigit(b[i+3]) {
262+
b[i] = dddToByte(b[i+1 : i+4])
263+
for j := i + 1; j < lb-3; j++ {
264+
b[j] = b[j+3]
265+
}
266+
lb -= 3
267+
}
268+
}
269+
}

labels_test.go

+37
Original file line numberDiff line numberDiff line change
@@ -334,3 +334,40 @@ func BenchmarkPrevLabelMixed(b *testing.B) {
334334
PrevLabel(`www\\\.example.com`, 10)
335335
}
336336
}
337+
338+
func TestCompare(t *testing.T) {
339+
domains := []string{ // based on an exanple from RFC 4034
340+
"example.",
341+
"a.example.",
342+
"yljkjljk.a.example.",
343+
"Z.a.example.",
344+
"zABC.a.EXAMPLE.",
345+
"a-.example.",
346+
"z.example.",
347+
"\001.z.example.",
348+
"*.z.example.",
349+
"\200.z.example.",
350+
}
351+
352+
len_domains := len(domains)
353+
354+
for i, domain := range domains {
355+
if i != 0 {
356+
prev_domain := domains[i-1]
357+
if !(Compare(prev_domain, domain) == -1 && Compare(domain, prev_domain) == 1) {
358+
t.Fatalf("prev comparison failure between %s and %s", prev_domain, domain)
359+
}
360+
}
361+
362+
if Compare(domain, domain) != 0 {
363+
t.Fatalf("self comparison failure for %s", domain)
364+
}
365+
366+
if i != len_domains-1 {
367+
next_domain := domains[i+1]
368+
if !(Compare(domain, next_domain) == -1 && Compare(next_domain, domain) == 1) {
369+
t.Fatalf("next comparison failure between %s and %s, %d and %d", domain, next_domain, Compare(domain, next_domain), Compare(next_domain, domain))
370+
}
371+
}
372+
}
373+
}

nsecx.go

+5
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,8 @@ func (rr *NSEC3) Match(name string) bool {
9393
}
9494
return false
9595
}
96+
97+
// Match returns true if the given name is covered by the NSEC record
98+
func (rr *NSEC) Cover(name string) bool {
99+
return Compare(rr.Hdr.Name, name) <= 0 && Compare(name, rr.NextDomain) == -1
100+
}

nsecx_test.go

+20
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,23 @@ func BenchmarkHashName(b *testing.B) {
168168
})
169169
}
170170
}
171+
172+
func TestNsecCover(t *testing.T) {
173+
nsec := testRR("aaa.ee. 3600 IN NSEC aac.ee. NS RRSIG NSEC").(*NSEC)
174+
175+
if !nsec.Cover("aaaa.ee.") {
176+
t.Fatal("nsec cover not covering in-range name")
177+
}
178+
179+
if !nsec.Cover("aaa.ee.") {
180+
t.Fatal("nsec cover not covering start of range")
181+
}
182+
183+
if nsec.Cover("aac.ee.") {
184+
t.Fatal("nsec cover range end failure")
185+
}
186+
187+
if nsec.Cover("aad.ee.") {
188+
t.Fatal("nsec cover covering out-of-range name")
189+
}
190+
}

0 commit comments

Comments
 (0)