Skip to content

Commit 302f19e

Browse files
committed
Add RFC4034 domain comparison + NSEC Cover
1 parent 5521648 commit 302f19e

File tree

4 files changed

+148
-0
lines changed

4 files changed

+148
-0
lines changed

labels.go

+86
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,79 @@ func PrevLabel(s string, n int) (i int, start bool) {
186186
return 0, n > 1
187187
}
188188

189+
// Compare compares domains according to the canonical ordering specified in RFC4034
190+
// returns an integer value similar to strcmp
191+
// (0 for equal values, -1 if s1 < s2, 1 if s1 > s2)
192+
func Compare(s1, s2 string) int {
193+
s1b := []byte(s1)
194+
s2b := []byte(s2)
195+
196+
doDDD(s1b)
197+
doDDD(s2b)
198+
199+
s1lend := len(s1)
200+
s2lend := len(s2)
201+
202+
for i := 0; ; i++ {
203+
s1lstart, end1 := PrevLabel(s1, i)
204+
s2lstart, end2 := PrevLabel(s2, i)
205+
206+
if end1 && end2 {
207+
return 0
208+
}
209+
210+
s1l := string(s1b[s1lstart:s1lend])
211+
s2l := string(s2b[s2lstart:s2lend])
212+
213+
if !equal(s1l, s2l) {
214+
return labelCompare(s1l, s2l)
215+
}
216+
217+
s1lend = s1lstart - 1
218+
s2lend = s2lstart - 1
219+
if s1lend == -1 {
220+
s1lend = 0
221+
}
222+
if s2lend == -1 {
223+
s2lend = 0
224+
}
225+
}
226+
}
227+
228+
// essentially strcasecmp
229+
// (0 for equal values, -1 if s1 < s2, 1 if s1 > s2)
230+
func labelCompare(a, b string) int {
231+
la := len(a)
232+
lb := len(b)
233+
minLen := la
234+
if lb < la {
235+
minLen = lb
236+
}
237+
for i := 0; i < minLen; i++ {
238+
ai := a[i]
239+
bi := b[i]
240+
if ai >= 'A' && ai <= 'Z' {
241+
ai |= 'a' - 'A'
242+
}
243+
if bi >= 'A' && bi <= 'Z' {
244+
bi |= 'a' - 'A'
245+
}
246+
if ai != bi {
247+
if ai > bi {
248+
return 1
249+
}
250+
return -1
251+
}
252+
}
253+
254+
if la > lb {
255+
return 1
256+
} else if la < lb {
257+
return -1
258+
}
259+
return 0
260+
}
261+
189262
// equal compares a and b while ignoring case. It returns true when equal otherwise false.
190263
func equal(a, b string) bool {
191264
// might be lifted into API function.
@@ -210,3 +283,16 @@ func equal(a, b string) bool {
210283
}
211284
return true
212285
}
286+
287+
func doDDD(b []byte) {
288+
lb := len(b)
289+
for i := 0; i < lb; i++ {
290+
if i+3 < lb && b[i] == '\\' && isDigit(b[i+1]) && isDigit(b[i+2]) && isDigit(b[i+3]) {
291+
b[i] = dddToByte(b[i+1 : i+4])
292+
for j := i + 1; j < lb-3; j++ {
293+
b[j] = b[j+3]
294+
}
295+
lb -= 3
296+
}
297+
}
298+
}

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)