master
go 324 lines 6.85 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package main
4
5 import (
6 "container/heap"
7 "fmt"
8 "net/netip"
9 "sort"
10 )
11
12 type asnValue struct {
13 asn uint32
14 org string
15 }
16
17 type geoValue struct {
18 country string
19 state string
20 city string
21 lat float64
22 lon float64
23 hasLoc bool
24 }
25
26 type intervalPriority struct {
27 sourceIndex int
28 rowIndex int
29 }
30
31 type intervalRecord[T comparable] struct {
32 start netip.Addr
33 end netip.Addr
34 value T
35 priority intervalPriority
36 }
37
38 type intervalEvent struct {
39 addr netip.Addr
40 kind intervalEventKind
41 id int
42 }
43
44 type intervalEventKind uint8
45
46 const (
47 intervalEventRemove intervalEventKind = iota
48 intervalEventAdd
49 )
50
51 type intervalHeap[T comparable] struct {
52 ids []int
53 records []intervalRecord[T]
54 }
55
56 func (h intervalHeap[T]) Len() int { return len(h.ids) }
57
58 func (h intervalHeap[T]) Less(i, j int) bool {
59 return betterPriority(
60 h.records[h.ids[i]].priority,
61 h.records[h.ids[j]].priority,
62 )
63 }
64
65 func (h intervalHeap[T]) Swap(i, j int) {
66 h.ids[i], h.ids[j] = h.ids[j], h.ids[i]
67 }
68
69 func (h *intervalHeap[T]) Push(x any) {
70 h.ids = append(h.ids, x.(int))
71 }
72
73 func (h *intervalHeap[T]) Pop() any {
74 last := len(h.ids) - 1
75 id := h.ids[last]
76 h.ids = h.ids[:last]
77 return id
78 }
79
80 func betterPriority(a, b intervalPriority) bool {
81 if a.sourceIndex != b.sourceIndex {
82 return a.sourceIndex < b.sourceIndex
83 }
84 return a.rowIndex > b.rowIndex
85 }
86
87 func mergeAsnSources(sources [][]asnRange) ([]asnRange, error) {
88 records := make([]intervalRecord[asnValue], 0)
89 for sourceIndex, ranges := range sources {
90 for rowIndex, rec := range ranges {
91 if err := rec.validate(); err != nil {
92 return nil, fmt.Errorf("asn source %d row %d: %w", sourceIndex, rowIndex, err)
93 }
94 records = append(records, intervalRecord[asnValue]{
95 start: rec.start,
96 end: rec.end,
97 value: asnValue{
98 asn: rec.asn,
99 org: rec.org,
100 },
101 priority: intervalPriority{
102 sourceIndex: sourceIndex,
103 rowIndex: rowIndex,
104 },
105 })
106 }
107 }
108
109 merged, err := mergeIntervals(records)
110 if err != nil {
111 return nil, err
112 }
113
114 out := make([]asnRange, 0, len(merged))
115 for _, rec := range merged {
116 out = append(out, asnRange{
117 start: rec.start,
118 end: rec.end,
119 asn: rec.value.asn,
120 org: rec.value.org,
121 })
122 }
123 return out, nil
124 }
125
126 func mergeGeoSources(sources [][]geoRange) ([]geoRange, error) {
127 records := make([]intervalRecord[geoValue], 0)
128 for sourceIndex, ranges := range sources {
129 for rowIndex, rec := range ranges {
130 if err := rec.validate(); err != nil {
131 return nil, fmt.Errorf("geo source %d row %d: %w", sourceIndex, rowIndex, err)
132 }
133 records = append(records, intervalRecord[geoValue]{
134 start: rec.start,
135 end: rec.end,
136 value: geoValue{
137 country: rec.country,
138 state: rec.state,
139 city: rec.city,
140 lat: rec.latitude,
141 lon: rec.longitude,
142 hasLoc: rec.hasLocation,
143 },
144 priority: intervalPriority{
145 sourceIndex: sourceIndex,
146 rowIndex: rowIndex,
147 },
148 })
149 }
150 }
151
152 merged, err := mergeIntervals(records)
153 if err != nil {
154 return nil, err
155 }
156
157 out := make([]geoRange, 0, len(merged))
158 for _, rec := range merged {
159 out = append(out, geoRange{
160 start: rec.start,
161 end: rec.end,
162 country: rec.value.country,
163 state: rec.value.state,
164 city: rec.value.city,
165 latitude: rec.value.lat,
166 longitude: rec.value.lon,
167 hasLocation: rec.value.hasLoc,
168 })
169 }
170 return out, nil
171 }
172
173 func mergeIntervals[T comparable](records []intervalRecord[T]) ([]intervalRecord[T], error) {
174 var v4 []intervalRecord[T]
175 var v6 []intervalRecord[T]
176
177 for _, rec := range records {
178 if !rec.start.IsValid() || !rec.end.IsValid() {
179 return nil, fmt.Errorf("invalid interval %v-%v", rec.start, rec.end)
180 }
181 if rec.start.BitLen() != rec.end.BitLen() {
182 return nil, fmt.Errorf("mixed address family range: %s-%s", rec.start, rec.end)
183 }
184 switch rec.start.BitLen() {
185 case 32:
186 v4 = append(v4, rec)
187 case 128:
188 v6 = append(v6, rec)
189 default:
190 return nil, fmt.Errorf("unsupported address family bitlen %d", rec.start.BitLen())
191 }
192 }
193
194 out := make([]intervalRecord[T], 0, len(records))
195 familyOut, err := mergeIntervalsForBitLen(v4, 32)
196 if err != nil {
197 return nil, err
198 }
199 out = append(out, familyOut...)
200
201 familyOut, err = mergeIntervalsForBitLen(v6, 128)
202 if err != nil {
203 return nil, err
204 }
205 out = append(out, familyOut...)
206
207 return out, nil
208 }
209
210 func mergeIntervalsForBitLen[T comparable](
211 records []intervalRecord[T],
212 bitLen int,
213 ) ([]intervalRecord[T], error) {
214 if len(records) == 0 {
215 return nil, nil
216 }
217
218 events := make([]intervalEvent, 0, len(records)*2)
219 for id, rec := range records {
220 events = append(events, intervalEvent{
221 addr: rec.start,
222 kind: intervalEventAdd,
223 id: id,
224 })
225 if next := rec.end.Next(); next.IsValid() {
226 events = append(events, intervalEvent{
227 addr: next,
228 kind: intervalEventRemove,
229 id: id,
230 })
231 }
232 }
233
234 sort.Slice(events, func(i, j int) bool {
235 if records[events[i].id].start.BitLen() != records[events[j].id].start.BitLen() {
236 return records[events[i].id].start.BitLen() < records[events[j].id].start.BitLen()
237 }
238 if cmp := compareAddrs(events[i].addr, events[j].addr); cmp != 0 {
239 return cmp < 0
240 }
241 return events[i].kind < events[j].kind
242 })
243
244 active := make(map[int]struct{}, len(records))
245 candidates := &intervalHeap[T]{records: records}
246 heap.Init(candidates)
247
248 maxAddr := maxAddrForBitLen(bitLen)
249 out := make([]intervalRecord[T], 0, len(records))
250
251 for i := 0; i < len(events); {
252 addr := events[i].addr
253 for i < len(events) && compareAddrs(events[i].addr, addr) == 0 {
254 switch events[i].kind {
255 case intervalEventRemove:
256 delete(active, events[i].id)
257 case intervalEventAdd:
258 active[events[i].id] = struct{}{}
259 heap.Push(candidates, events[i].id)
260 }
261 i++
262 }
263
264 bestID, ok := topActiveRecord(candidates, active)
265 if !ok {
266 continue
267 }
268
269 end := maxAddr
270 if i < len(events) {
271 end = events[i].addr.Prev()
272 }
273 if compareAddrs(addr, end) > 0 {
274 continue
275 }
276
277 best := records[bestID]
278 next := intervalRecord[T]{
279 start: addr,
280 end: end,
281 value: best.value,
282 priority: best.priority,
283 }
284 if len(out) > 0 &&
285 out[len(out)-1].value == next.value &&
286 out[len(out)-1].end.Next() == next.start {
287 out[len(out)-1].end = next.end
288 continue
289 }
290 out = append(out, next)
291 }
292
293 return out, nil
294 }
295
296 func topActiveRecord[T comparable](
297 candidates *intervalHeap[T],
298 active map[int]struct{},
299 ) (int, bool) {
300 for candidates.Len() > 0 {
301 id := candidates.ids[0]
302 if _, ok := active[id]; ok {
303 return id, true
304 }
305 heap.Pop(candidates)
306 }
307 return 0, false
308 }
309
310 func maxAddrForBitLen(bitLen int) netip.Addr {
311 switch bitLen {
312 case 32:
313 return netip.AddrFrom4([4]byte{0xff, 0xff, 0xff, 0xff})
314 case 128:
315 return netip.AddrFrom16([16]byte{
316 0xff, 0xff, 0xff, 0xff,
317 0xff, 0xff, 0xff, 0xff,
318 0xff, 0xff, 0xff, 0xff,
319 0xff, 0xff, 0xff, 0xff,
320 })
321 default:
322 return netip.Addr{}
323 }
324 }