-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdnstrie.go
139 lines (123 loc) · 3.64 KB
/
dnstrie.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// Package dnstrie creates a DNS-aware trie for fast filtering of domain names
// based on exact matches and wildcarded zone cut matches against a slice of
// matching criteria. For example, providing "*.org", "google.com",
// "*.mail.google.com", and "+.web.google.com" would construct a trie like:
//
// tree
// +-- org
// +-- *
// +-- com
// +-- google
// +-- mail
// +-- +
// +-- web
// +-- *
// Where google.com, web.google.com (and all its children) and anything under
// but not including org, mail.google.com match the tree (with `tree.Match`).
package dnstrie
import (
"fmt"
"strings"
)
// DomainTrie is a struct for the recursive DNS-aware trie data structure. The
// three members represent the current label ("." for the root), the list of
// children and if this label can be considered an ending state for the tree (to
// identify that there is an exact domain match at this point). This should not
// be used directly and should instead be created using `dnstrie.MakeTrie`.
type DomainTrie struct {
label string
others domainTrieSlice
end bool
}
type domainTrieSlice []*DomainTrie
// Empty returns true if nothing has been added to the trie and true otherwise.
func (root *DomainTrie) Empty() bool {
return root.others == nil && !root.end
}
// Match matches against exactly fully qualified domain names and zone
// wildcards.
func (root *DomainTrie) Match(domain string) bool {
reversedLabels, err := reverseLabelSlice(domain)
if err != nil {
return false
}
curr := root
for _, label := range reversedLabels {
node := findNode("+", curr.others)
if node != nil {
return true
}
node = findNode(label, curr.others)
if node == nil {
return false
}
curr = node
}
return curr.end
}
func findNode(label string, others domainTrieSlice) *DomainTrie {
for _, trie := range others {
if trie.label == label {
return trie
}
}
return nil
}
func checkAndRemoveWildcard(domain string) (string, string) {
if len(domain) < 2 {
return domain, ""
}
if domain[1] == '.' && (domain[0] == '*' || domain[0] == '+') {
return domain[2:], string(domain[0])
}
return domain, ""
}
func reverseLabelSlice(domain string) ([]string, error) {
var reversedLabels []string
domain, wildcard := checkAndRemoveWildcard(domain)
labels := strings.Split(domain, ".")
for i := len(labels) - 1; i >= 0; i-- {
reversedLabels = append(reversedLabels, labels[i])
}
if wildcard != "" {
reversedLabels = append(reversedLabels, wildcard)
}
return reversedLabels, nil
}
func addReversedLabelsToTrie(root *DomainTrie, reversedLabels []string) {
curr := root
for _, label := range reversedLabels {
node := findNode(label, curr.others)
if node == nil {
node = &DomainTrie{label, domainTrieSlice{}, false}
curr.others = append(curr.others, node)
}
curr = node
}
curr.end = true
}
// MakeTrie returns the root of a trie given a slice of domain names. Use
// dns.Normalize to prepare domains received from untrusted or unreliable
// sources.
func MakeTrie(domains []string) (*DomainTrie, error) {
root := &DomainTrie{label: "."}
for _, d := range domains {
reversedLabels, err := reverseLabelSlice(d)
length := len(reversedLabels)
if err != nil {
return nil, fmt.Errorf("Failed to build DomainTrie: %v", err)
}
// If it was a star, we need to add it both without the wildcard
// for the exact match and with "+" for the normal wildcard
// match.
wasStar := reversedLabels[length-1] == "*"
if wasStar {
reversedLabels[length-1] = "+"
}
addReversedLabelsToTrie(root, reversedLabels)
if wasStar {
addReversedLabelsToTrie(root, reversedLabels[:length-1])
}
}
return root, nil
}