-
Notifications
You must be signed in to change notification settings - Fork 65
/
blocklistdb-domain.go
118 lines (104 loc) · 2.71 KB
/
blocklistdb-domain.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
package rdns
import (
"fmt"
"net"
"strings"
"github.com/miekg/dns"
)
// DomainDB holds a list of domain strings (potentially with wildcards). Matching
// logic:
// domain.com: matches just domain.com and not subdomains
// .domain.com: matches domain.com and all subdomains
// *.domain.com: matches all subdomains but not domain.com
type DomainDB struct {
name string
root node
loader BlocklistLoader
}
type node map[string]node
var _ BlocklistDB = &DomainDB{}
// NewDomainDB returns a new instance of a matcher for a list of regular expressions.
func NewDomainDB(name string, loader BlocklistLoader) (*DomainDB, error) {
rules, err := loader.Load()
if err != nil {
return nil, err
}
root := make(node)
for _, r := range rules {
r = strings.TrimSpace(r)
// Strip trailing . in case the list has FQDN names with . suffixes.
r = strings.TrimSuffix(r, ".")
// Break up the domain into its parts and iterate backwards over them, building
// a graph of maps
parts := strings.Split(r, ".")
n := root
for i := len(parts) - 1; i >= 0; i-- {
part := parts[i]
// Only allow wildcards as the first domain part, and not in a string
if strings.Contains(part, "*") && (i > 0 || len(part) != 1) {
return nil, fmt.Errorf("invalid blocklist item: '%s'", part)
}
subNode, ok := n[part]
if !ok {
subNode = make(node)
n[part] = subNode
}
n = subNode
}
}
return &DomainDB{name, root, loader}, nil
}
func (m *DomainDB) Reload() (BlocklistDB, error) {
return NewDomainDB(m.name, m.loader)
}
func (m *DomainDB) Match(q dns.Question) ([]net.IP, []string, *BlocklistMatch, bool) {
s := strings.TrimSuffix(q.Name, ".")
var matched []string
parts := strings.Split(s, ".")
n := m.root
for i := len(parts) - 1; i >= 0; i-- {
part := parts[i]
subNode, ok := n[part]
if !ok {
return nil, nil, nil, false
}
matched = append(matched, part)
if _, ok := subNode[""]; ok { // exact and sub-domain match
return nil,
nil,
&BlocklistMatch{
List: m.name,
Rule: matchedDomainParts(".", matched),
},
true
}
if _, ok := subNode["*"]; ok && i > 0 { // wildcard match on sub-domains
return nil,
nil,
&BlocklistMatch{
List: m.name,
Rule: matchedDomainParts("*.", matched),
},
true
}
n = subNode
}
return nil,
nil,
&BlocklistMatch{
List: m.name,
Rule: matchedDomainParts("", matched),
},
len(n) == 0 // exact match
}
func (m *DomainDB) String() string {
return "Domain"
}
// Turn a list of matched domain fragments into a domain (rule)
func matchedDomainParts(prefix string, p []string) string {
for i := len(p)/2 - 1; i >= 0; i-- {
opp := len(p) - 1 - i
p[i], p[opp] = p[opp], p[i]
}
return prefix + strings.Join(p, ".")
}