master
go 261 lines 6.08 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package iprange
4
5 import (
6 "iter"
7 "math/big"
8 "net/netip"
9 "sort"
10 "strings"
11 )
12
13 // Pool represents a collection of IP ranges.
14 // It provides methods to work with multiple ranges as a single unit.
15 type Pool struct {
16 ranges []Range
17 }
18
19 // NewPool creates a new Pool from the given ranges.
20 // It filters out nil ranges and makes a defensive copy of the slice.
21 func NewPool(ranges ...Range) *Pool {
22 filtered := make([]Range, 0, len(ranges))
23 for _, r := range ranges {
24 if r != nil {
25 filtered = append(filtered, r)
26 }
27 }
28 return &Pool{ranges: filtered}
29 }
30
31 // ParsePool parses a string containing multiple IP ranges and returns a Pool.
32 // The string should contain space-separated range specifications.
33 func ParsePool(s string) (*Pool, error) {
34 ranges, err := ParseRanges(s)
35 if err != nil {
36 return nil, err
37 }
38 return NewPool(ranges...), nil
39 }
40
41 // Ranges returns a copy of the ranges in the pool.
42 func (p *Pool) Ranges() []Range {
43 if p == nil || len(p.ranges) == 0 {
44 return nil
45 }
46
47 result := make([]Range, len(p.ranges))
48 copy(result, p.ranges)
49 return result
50 }
51
52 // Len returns the number of ranges in the pool.
53 func (p *Pool) Len() int {
54 if p == nil {
55 return 0
56 }
57 return len(p.ranges)
58 }
59
60 // IsEmpty reports whether the pool contains no ranges.
61 func (p *Pool) IsEmpty() bool {
62 return p.Len() == 0
63 }
64
65 // String returns the string representation of the pool.
66 // Ranges are separated by spaces.
67 func (p *Pool) String() string {
68 if p == nil || len(p.ranges) == 0 {
69 return ""
70 }
71
72 parts := make([]string, len(p.ranges))
73 for i, r := range p.ranges {
74 parts[i] = r.String()
75 }
76 return strings.Join(parts, " ")
77 }
78
79 // Size returns the total number of IP addresses across all ranges in the pool.
80 // Note: This does not account for overlapping ranges.
81 func (p *Pool) Size() *big.Int {
82 if p == nil || len(p.ranges) == 0 {
83 return big.NewInt(0)
84 }
85
86 total := new(big.Int)
87 for _, r := range p.ranges {
88 total.Add(total, r.Size())
89 }
90 return total
91 }
92
93 // Contains reports whether any range in the pool includes the given IP address.
94 func (p *Pool) Contains(ip netip.Addr) bool {
95 if p == nil || !ip.IsValid() {
96 return false
97 }
98
99 for _, r := range p.ranges {
100 if r.Contains(ip) {
101 return true
102 }
103 }
104 return false
105 }
106
107 // ContainsRange reports whether the pool fully contains the given range.
108 // This is true if every IP in the given range is contained in at least one range in the pool.
109 // Note: For large ranges, this method may be expensive as it needs to verify coverage
110 // of all addresses in the range.
111 func (p *Pool) ContainsRange(r Range) bool {
112 if p == nil || r == nil {
113 return false
114 }
115
116 // Quick check: if start or end is not in pool, range can't be contained
117 if !p.Contains(r.Start()) || !p.Contains(r.End()) {
118 return false
119 }
120
121 // For efficiency, check if the range is fully contained within
122 // any single range in the pool
123 for _, poolRange := range p.ranges {
124 if rangeFullyContains(poolRange, r) {
125 return true
126 }
127 }
128
129 // If not in a single range, we need to check if the range is covered
130 // by multiple pool ranges without gaps.
131
132 // For small ranges (up to 256 IPs), check every address for completeness.
133 // This handles cases like a /24 network efficiently while being 100% accurate
134 size := r.Size()
135 if size.Cmp(big.NewInt(256)) <= 0 {
136 for addr := range r.Iterate() {
137 if !p.Contains(addr) {
138 return false
139 }
140 }
141 return true
142 }
143
144 // For larger ranges, we need a more efficient approach.
145 // Sort pool ranges and check for coverage without gaps.
146 return p.checkRangeCoverage(r)
147 }
148
149 // rangeFullyContains returns true if r1 fully contains r2
150 func rangeFullyContains(r1, r2 Range) bool {
151 return r1.Start().Compare(r2.Start()) <= 0 && r1.End().Compare(r2.End()) >= 0
152 }
153
154 // checkRangeCoverage efficiently checks if a range is fully covered by the pool's ranges
155 func (p *Pool) checkRangeCoverage(r Range) bool {
156 // Filter pool ranges that might overlap with r
157 var relevant []Range
158 for _, pr := range p.ranges {
159 // Check if pr overlaps with r
160 if pr.End().Compare(r.Start()) >= 0 && pr.Start().Compare(r.End()) <= 0 {
161 relevant = append(relevant, pr)
162 }
163 }
164
165 if len(relevant) == 0 {
166 return false
167 }
168
169 sort.Slice(relevant, func(i, j int) bool {
170 return relevant[i].Start().Compare(relevant[j].Start()) < 0
171 })
172
173 // Check if the sorted ranges cover r without gaps
174 currentCoverage := r.Start()
175
176 for _, pr := range relevant {
177 // If there's a gap between current coverage and this range
178 if pr.Start().Compare(currentCoverage) > 0 {
179 return false
180 }
181
182 // Extend coverage if this range extends it
183 if pr.End().Compare(currentCoverage) >= 0 {
184 currentCoverage = pr.End()
185
186 // If we've covered the entire range, we're done
187 if currentCoverage.Compare(r.End()) >= 0 {
188 return true
189 }
190
191 // Move to next address for continuous coverage check
192 next := currentCoverage.Next()
193 if next.IsValid() {
194 currentCoverage = next
195 }
196 }
197 }
198
199 // Check if we covered up to or past the end
200 return currentCoverage.Compare(r.End()) > 0
201 }
202
203 // Iterate returns an iterator over all IP addresses in all ranges.
204 // Note: If ranges overlap, addresses in the overlap will be yielded multiple times.
205 func (p *Pool) Iterate() iter.Seq[netip.Addr] {
206 return func(yield func(netip.Addr) bool) {
207 if p == nil {
208 return
209 }
210
211 for _, r := range p.ranges {
212 for addr := range r.Iterate() {
213 if !yield(addr) {
214 return
215 }
216 }
217 }
218 }
219 }
220
221 // IterateRanges returns an iterator over all ranges in the pool.
222 func (p *Pool) IterateRanges() iter.Seq[Range] {
223 return func(yield func(Range) bool) {
224 if p == nil {
225 return
226 }
227
228 for _, r := range p.ranges {
229 if !yield(r) {
230 return
231 }
232 }
233 }
234 }
235
236 // Add appends one or more ranges to the pool.
237 func (p *Pool) Add(ranges ...Range) {
238 for _, r := range ranges {
239 if r != nil {
240 p.ranges = append(p.ranges, r)
241 }
242 }
243 }
244
245 // AddString parses the string as IP ranges and adds them to the pool.
246 func (p *Pool) AddString(s string) error {
247 ranges, err := ParseRanges(s)
248 if err != nil {
249 return err
250 }
251 p.Add(ranges...)
252 return nil
253 }
254
255 // Clone returns a deep copy of the pool.
256 func (p *Pool) Clone() *Pool {
257 if p == nil {
258 return nil
259 }
260 return NewPool(p.ranges...)
261 }