Skip to content

Commit

Permalink
Add RFC4034 domain comparison + NSEC Cover
Browse files Browse the repository at this point in the history
  • Loading branch information
monoidic committed May 17, 2022
1 parent 5521648 commit 935818f
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 0 deletions.
86 changes: 86 additions & 0 deletions labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,79 @@ func PrevLabel(s string, n int) (i int, start bool) {
return 0, n > 1
}

// Compare compares domains according to the canonical ordering specified in RFC4034
// returns an integer value similar to strcmp
// (0 for equal values, -1 if s1 < s2, 1 if s1 > s2)
func Compare(s1, s2 string) int {
s1b := []byte(s1)
s2b := []byte(s2)

doDDD(s1b)
doDDD(s2b)

s1lend := len(s1)
s2lend := len(s2)

for i := 0; ; i++ {
s1lstart, end1 := PrevLabel(s1, i)
s2lstart, end2 := PrevLabel(s2, i)

if end1 && end2 {
return 0
}

s1l := string(s1b[s1lstart:s1lend])
s2l := string(s2b[s2lstart:s2lend])

if cmp := labelCompare(s1l, s2l); cmp != 0 {
return cmp
}

s1lend = s1lstart - 1
s2lend = s2lstart - 1
if s1lend == -1 {
s1lend = 0
}
if s2lend == -1 {
s2lend = 0
}
}
}

// essentially strcasecmp
// (0 for equal values, -1 if s1 < s2, 1 if s1 > s2)
func labelCompare(a, b string) int {
la := len(a)
lb := len(b)
minLen := la
if lb < la {
minLen = lb
}
for i := 0; i < minLen; i++ {
ai := a[i]
bi := b[i]
if ai >= 'A' && ai <= 'Z' {
ai |= 'a' - 'A'
}
if bi >= 'A' && bi <= 'Z' {
bi |= 'a' - 'A'
}
if ai != bi {
if ai > bi {
return 1
}
return -1
}
}

if la > lb {
return 1
} else if la < lb {
return -1
}
return 0
}

// equal compares a and b while ignoring case. It returns true when equal otherwise false.
func equal(a, b string) bool {
// might be lifted into API function.
Expand All @@ -210,3 +283,16 @@ func equal(a, b string) bool {
}
return true
}

func doDDD(b []byte) {
lb := len(b)
for i := 0; i < lb; i++ {
if i+3 < lb && b[i] == '\\' && isDigit(b[i+1]) && isDigit(b[i+2]) && isDigit(b[i+3]) {
b[i] = dddToByte(b[i+1 : i+4])
for j := i + 1; j < lb-3; j++ {
b[j] = b[j+3]
}
lb -= 3
}
}
}
37 changes: 37 additions & 0 deletions labels_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,3 +334,40 @@ func BenchmarkPrevLabelMixed(b *testing.B) {
PrevLabel(`www\\\.example.com`, 10)
}
}

func TestCompare(t *testing.T) {
domains := []string{ // based on an exanple from RFC 4034
"example.",
"a.example.",
"yljkjljk.a.example.",
"Z.a.example.",
"zABC.a.EXAMPLE.",
"a-.example.",
"z.example.",
"\001.z.example.",
"*.z.example.",
"\200.z.example.",
}

len_domains := len(domains)

for i, domain := range domains {
if i != 0 {
prev_domain := domains[i-1]
if !(Compare(prev_domain, domain) == -1 && Compare(domain, prev_domain) == 1) {
t.Fatalf("prev comparison failure between %s and %s", prev_domain, domain)
}
}

if Compare(domain, domain) != 0 {
t.Fatalf("self comparison failure for %s", domain)
}

if i != len_domains-1 {
next_domain := domains[i+1]
if !(Compare(domain, next_domain) == -1 && Compare(next_domain, domain) == 1) {
t.Fatalf("next comparison failure between %s and %s, %d and %d", domain, next_domain, Compare(domain, next_domain), Compare(next_domain, domain))
}
}
}
}
5 changes: 5 additions & 0 deletions nsecx.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,8 @@ func (rr *NSEC3) Match(name string) bool {
}
return false
}

// Match returns true if the given name is covered by the NSEC record
func (rr *NSEC) Cover(name string) bool {
return Compare(rr.Hdr.Name, name) <= 0 && Compare(name, rr.NextDomain) == -1
}
20 changes: 20 additions & 0 deletions nsecx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,23 @@ func BenchmarkHashName(b *testing.B) {
})
}
}

func TestNsecCover(t *testing.T) {
nsec := testRR("aaa.ee. 3600 IN NSEC aac.ee. NS RRSIG NSEC").(*NSEC)

if !nsec.Cover("aaaa.ee.") {
t.Fatal("nsec cover not covering in-range name")
}

if !nsec.Cover("aaa.ee.") {
t.Fatal("nsec cover not covering start of range")
}

if nsec.Cover("aac.ee.") {
t.Fatal("nsec cover range end failure")
}

if nsec.Cover("aad.ee.") {
t.Fatal("nsec cover covering out-of-range name")
}
}

0 comments on commit 935818f

Please sign in to comment.