master
go 148 lines 3.31 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package main
4
5 import (
6 "bytes"
7 "errors"
8 "fmt"
9 "net/netip"
10 "sort"
11 "strings"
12 )
13
14 type ipClass string
15
16 const (
17 ipClassPublic ipClass = "public"
18 ipClassInteresting ipClass = "interesting"
19 ipClassPrivate ipClass = "private"
20 ipClassLocalhost ipClass = "localhost"
21 )
22
23 type asnRange struct {
24 start netip.Addr
25 end netip.Addr
26 asn uint32
27 org string
28 }
29
30 type geoRange struct {
31 start netip.Addr
32 end netip.Addr
33
34 country string
35 state string
36 city string
37
38 latitude float64
39 longitude float64
40 hasLocation bool
41 }
42
43 type classification struct {
44 prefixes []netip.Prefix
45 class ipClass
46 }
47
48 func (r asnRange) validate() error {
49 if !r.start.IsValid() || !r.end.IsValid() {
50 return errors.New("invalid range address")
51 }
52 if r.start.BitLen() != r.end.BitLen() {
53 return fmt.Errorf("mixed address family range: %s-%s", r.start, r.end)
54 }
55 if compareAddrs(r.start, r.end) > 0 {
56 return fmt.Errorf("range start %s is after end %s", r.start, r.end)
57 }
58 return nil
59 }
60
61 func (r geoRange) validate() error {
62 if !r.start.IsValid() || !r.end.IsValid() {
63 return errors.New("invalid range address")
64 }
65 if r.start.BitLen() != r.end.BitLen() {
66 return fmt.Errorf("mixed address family range: %s-%s", r.start, r.end)
67 }
68 if compareAddrs(r.start, r.end) > 0 {
69 return fmt.Errorf("range start %s is after end %s", r.start, r.end)
70 }
71 if r.country != "" && len(r.country) != 2 {
72 return fmt.Errorf("country code must be 2 chars: %q", r.country)
73 }
74 return nil
75 }
76
77 func compareAddrs(a, b netip.Addr) int {
78 aa := a.As16()
79 bb := b.As16()
80 return bytes.Compare(aa[:], bb[:])
81 }
82
83 func normalizeCountry(country string) string {
84 country = strings.TrimSpace(strings.ToUpper(country))
85 if len(country) != 2 {
86 return ""
87 }
88 if country == "--" {
89 return ""
90 }
91 for _, r := range country {
92 if r < 'A' || r > 'Z' {
93 return ""
94 }
95 }
96 return country
97 }
98
99 func classifyRanges(policy policyConfig) ([]classification, error) {
100 res := make([]classification, 0, 3)
101 appendSet := func(cidrs []string, class ipClass) error {
102 if len(cidrs) == 0 {
103 return nil
104 }
105 prefixes := make([]netip.Prefix, 0, len(cidrs))
106 for _, cidr := range cidrs {
107 cidr = strings.TrimSpace(cidr)
108 if cidr == "" {
109 continue
110 }
111 p, err := netip.ParsePrefix(cidr)
112 if err != nil {
113 return fmt.Errorf("invalid %s cidr %q: %w", class, cidr, err)
114 }
115 prefixes = append(prefixes, p.Masked())
116 }
117 if len(prefixes) == 0 {
118 return nil
119 }
120
121 sort.Slice(prefixes, func(i, j int) bool {
122 if prefixes[i].Addr().BitLen() != prefixes[j].Addr().BitLen() {
123 return prefixes[i].Addr().BitLen() < prefixes[j].Addr().BitLen()
124 }
125 if prefixes[i].Bits() != prefixes[j].Bits() {
126 return prefixes[i].Bits() < prefixes[j].Bits()
127 }
128 return compareAddrs(prefixes[i].Addr(), prefixes[j].Addr()) < 0
129 })
130 res = append(res, classification{prefixes: prefixes, class: class})
131 return nil
132 }
133
134 if err := appendSet(policy.interestingCIDRs, ipClassInteresting); err != nil {
135 return nil, err
136 }
137 if err := appendSet(policy.privateCIDRs, ipClassPrivate); err != nil {
138 return nil, err
139 }
140 if err := appendSet(policy.localhostCIDRs, ipClassLocalhost); err != nil {
141 return nil, err
142 }
143 return res, nil
144 }
145
146 func trackIndividual(class ipClass) bool {
147 return class == ipClassInteresting || class == ipClassPrivate || class == ipClassLocalhost
148 }