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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package routing
import (
"bytes"
"fmt"
"net/netip"
"regexp"
"sort"
"strings"
)
// DomainSet matches host names. Full and suffix names are map lookups (one
// per label of the name), so the size of the list does not slow matching;
// keywords and regular expressions, rare in practice, are checked in turn.
type DomainSet struct {
full map[string]struct{}
suffix map[string]struct{}
keywords []string
regexps []*regexp.Regexp
}
func newDomainSet() *DomainSet {
return &DomainSet{full: make(map[string]struct{}), suffix: make(map[string]struct{})}
}
func (s *DomainSet) add(kind int, value string) error {
value = strings.ToLower(strings.TrimSuffix(strings.TrimSpace(value), "."))
if value == "" {
return nil
}
switch kind {
case domainFull:
s.full[value] = struct{}{}
case domainSuffix:
s.suffix[value] = struct{}{}
case domainPlain:
s.keywords = append(s.keywords, value)
case domainRegex:
re, err := regexp.Compile(value)
if err != nil {
return fmt.Errorf("regexp %q: %w", value, err)
}
s.regexps = append(s.regexps, re)
default:
return fmt.Errorf("unknown domain type %d", kind)
}
return nil
}
// Len is the number of entries.
func (s *DomainSet) Len() int {
return len(s.full) + len(s.suffix) + len(s.keywords) + len(s.regexps)
}
// Contains reports whether host (lower case, without a trailing dot)
// matches.
func (s *DomainSet) Contains(host string) bool {
if _, ok := s.full[host]; ok {
return true
}
for name := host; ; {
if _, ok := s.suffix[name]; ok {
return true
}
dot := strings.IndexByte(name, '.')
if dot < 0 {
break
}
name = name[dot+1:]
}
for _, k := range s.keywords {
if strings.Contains(host, k) {
return true
}
}
for _, re := range s.regexps {
if re.MatchString(host) {
return true
}
}
return false
}
// IPSet matches addresses against sorted, merged ranges with a binary
// search.
type IPSet struct {
prefixes []netip.Prefix
v4 []ipRange
v6 []ipRange
}
type ipRange struct{ lo, hi [16]byte }
func (s *IPSet) add(p netip.Prefix) { s.prefixes = append(s.prefixes, p) }
// Len is the number of merged ranges.
func (s *IPSet) Len() int { return len(s.v4) + len(s.v6) }
// finish turns the added prefixes into merged, sorted ranges.
func (s *IPSet) finish() {
var v4, v6 []ipRange
for _, p := range s.prefixes {
r := prefixRange(p)
if p.Addr().Is4() {
v4 = append(v4, r)
} else {
v6 = append(v6, r)
}
}
s.v4, s.v6, s.prefixes = mergeRanges(v4), mergeRanges(v6), nil
}
func prefixRange(p netip.Prefix) ipRange {
p = p.Masked()
lo := p.Addr().As16()
hi := lo
bits := p.Bits()
if p.Addr().Is4() {
bits += 96
}
for i := bits; i < 128; i++ {
hi[i/8] |= 1 << (7 - uint(i%8))
}
return ipRange{lo, hi}
}
func mergeRanges(rs []ipRange) []ipRange {
sort.Slice(rs, func(i, j int) bool { return bytes.Compare(rs[i].lo[:], rs[j].lo[:]) < 0 })
var out []ipRange
for _, r := range rs {
if n := len(out); n > 0 {
if after := next(out[n-1].hi); bytes.Compare(r.lo[:], after[:]) <= 0 {
if bytes.Compare(r.hi[:], out[n-1].hi[:]) > 0 {
out[n-1].hi = r.hi
}
continue
}
}
out = append(out, r)
}
return out
}
// next returns a+1, or a when a is the last address.
func next(a [16]byte) [16]byte {
for i := 15; i >= 0; i-- {
a[i]++
if a[i] != 0 {
return a
}
}
return [16]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
}
// Contains reports whether addr is in the set.
func (s *IPSet) Contains(addr netip.Addr) bool {
addr = addr.Unmap()
rs := s.v6
if addr.Is4() {
rs = s.v4
}
a := addr.As16()
i := sort.Search(len(rs), func(i int) bool { return bytes.Compare(rs[i].hi[:], a[:]) >= 0 })
return i < len(rs) && bytes.Compare(rs[i].lo[:], a[:]) <= 0
}