1use std::collections::HashMap;
2use std::net::IpAddr;
3
4use dashmap::DashSet;
5use ipnet::IpNet;
6
7use quincy::config::AddressRange;
8use quincy::error::{AuthError, Result};
9
10pub struct AddressPool {
15 ranges: Vec<AddressRange>,
17 used_addresses: DashSet<IpAddr>,
19}
20
21impl AddressPool {
22 pub fn new(ranges: Vec<AddressRange>) -> Self {
27 Self {
28 ranges,
29 used_addresses: DashSet::new(),
30 }
31 }
32
33 pub fn next_available_address(&self) -> Option<IpAddr> {
39 self.ranges
40 .iter()
41 .flat_map(|range| range.into_inner())
42 .find(|address| self.used_addresses.insert(*address))
43 }
44
45 pub fn release_address(&self, address: &IpAddr) {
50 self.used_addresses.remove(address);
51 }
52
53 pub fn reserve_addresses(&self, addresses: impl Iterator<Item = IpAddr>) {
58 for address in addresses {
59 self.used_addresses.insert(address);
60 }
61 }
62}
63
64pub struct AddressPoolManager {
72 network: IpNet,
74 global_pool: AddressPool,
76 user_pools: HashMap<String, AddressPool>,
78}
79
80impl AddressPoolManager {
81 pub fn new(network: IpNet, user_pools: HashMap<String, Vec<AddressRange>>) -> Result<Self> {
103 let global_pool = AddressPool::new(vec![AddressRange::from(network)]);
105
106 let reserved = [network.network(), network.addr(), network.broadcast()];
108 global_pool.reserve_addresses(reserved.iter().copied());
109
110 let mut built_user_pools = HashMap::with_capacity(user_pools.len());
112
113 for (username, ranges) in &user_pools {
114 for range in ranges {
117 for address in range.into_inner() {
118 if !network.contains(&address) {
119 return Err(AuthError::InvalidUserStore {
120 reason: format!(
121 "user '{username}': address {address} is outside \
122 tunnel network {network}"
123 ),
124 }
125 .into());
126 }
127 if reserved.contains(&address) {
128 return Err(AuthError::InvalidUserStore {
129 reason: format!(
130 "user '{username}': address {address} is a reserved \
131 tunnel address (network, server, or broadcast)"
132 ),
133 }
134 .into());
135 }
136 }
137 }
138
139 global_pool.reserve_addresses(ranges.iter().flat_map(|range| range.into_inner()));
141
142 built_user_pools.insert(username.clone(), AddressPool::new(ranges.clone()));
143 }
144
145 Ok(Self {
146 network,
147 global_pool,
148 user_pools: built_user_pools,
149 })
150 }
151
152 pub fn allocate_address(&self, username: &str) -> Option<IpNet> {
161 let address = match self.user_pools.get(username) {
162 Some(user_pool) => user_pool.next_available_address()?,
163 None => self.global_pool.next_available_address()?,
164 };
165
166 Some(
167 IpNet::with_netmask(address, self.network.netmask())
168 .expect("Netmask is always valid for addresses within the tunnel network"),
169 )
170 }
171
172 pub fn release_address(&self, username: &str, address: &IpAddr) {
181 match self.user_pools.get(username) {
182 Some(user_pool) => user_pool.release_address(address),
183 None => self.global_pool.release_address(address),
184 }
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use ipnet::{IpNet, Ipv4Net};
192 use quincy::config::AddressRange;
193 use std::collections::HashMap;
194 use std::net::Ipv4Addr;
195
196 fn test_network() -> IpNet {
198 IpNet::V4(
199 Ipv4Net::with_netmask(
200 Ipv4Addr::new(10, 0, 0, 1),
201 Ipv4Addr::new(255, 255, 255, 248),
202 )
203 .unwrap(),
204 )
205 }
206
207 #[test]
210 fn pool_allocates_in_order() {
211 let ranges = vec!["10.0.0.2 - 10.0.0.4".parse::<AddressRange>().unwrap()];
212 let pool = AddressPool::new(ranges);
213
214 assert_eq!(
215 pool.next_available_address(),
216 Some(Ipv4Addr::new(10, 0, 0, 2).into())
217 );
218 assert_eq!(
219 pool.next_available_address(),
220 Some(Ipv4Addr::new(10, 0, 0, 3).into())
221 );
222 assert_eq!(
223 pool.next_available_address(),
224 Some(Ipv4Addr::new(10, 0, 0, 4).into())
225 );
226 assert_eq!(pool.next_available_address(), None);
227 }
228
229 #[test]
230 fn pool_release_and_reallocate() {
231 let ranges = vec!["10.0.0.2/32".parse::<AddressRange>().unwrap()];
232 let pool = AddressPool::new(ranges);
233
234 let addr = pool.next_available_address().unwrap();
235 assert_eq!(pool.next_available_address(), None);
236
237 pool.release_address(&addr);
238 assert_eq!(pool.next_available_address(), Some(addr));
239 }
240
241 #[test]
242 fn pool_reserve_addresses() {
243 let ranges = vec!["10.0.0.2 - 10.0.0.4".parse::<AddressRange>().unwrap()];
244 let pool = AddressPool::new(ranges);
245 pool.reserve_addresses([Ipv4Addr::new(10, 0, 0, 3).into()].into_iter());
246
247 assert_eq!(
248 pool.next_available_address(),
249 Some(Ipv4Addr::new(10, 0, 0, 2).into())
250 );
251 assert_eq!(
253 pool.next_available_address(),
254 Some(Ipv4Addr::new(10, 0, 0, 4).into())
255 );
256 assert_eq!(pool.next_available_address(), None);
257 }
258
259 #[test]
262 fn manager_global_pool_excludes_reserved() {
263 let user_pools = HashMap::from([(
264 "alice".to_string(),
265 vec!["10.0.0.2/32".parse::<AddressRange>().unwrap()],
266 )]);
267 let manager = AddressPoolManager::new(test_network(), user_pools).unwrap();
268
269 let addr = manager.allocate_address("bob").unwrap();
272 assert_eq!(addr.addr(), IpAddr::from(Ipv4Addr::new(10, 0, 0, 3)));
273 }
274
275 #[test]
276 fn manager_user_pool_allocates_from_reserved() {
277 let user_pools = HashMap::from([(
278 "alice".to_string(),
279 vec!["10.0.0.5 - 10.0.0.6".parse::<AddressRange>().unwrap()],
280 )]);
281 let manager = AddressPoolManager::new(test_network(), user_pools).unwrap();
282
283 let addr = manager.allocate_address("alice").unwrap();
284 assert_eq!(addr.addr(), IpAddr::from(Ipv4Addr::new(10, 0, 0, 5)));
285 }
286
287 #[test]
288 fn manager_user_pool_exhaustion() {
289 let user_pools = HashMap::from([(
290 "alice".to_string(),
291 vec!["10.0.0.5/32".parse::<AddressRange>().unwrap()],
292 )]);
293 let manager = AddressPoolManager::new(test_network(), user_pools).unwrap();
294
295 assert!(manager.allocate_address("alice").is_some());
296 assert!(manager.allocate_address("alice").is_none());
297 assert!(manager.allocate_address("bob").is_some());
299 }
300
301 #[test]
302 fn manager_release_user_pool_and_reallocate() {
303 let user_pools = HashMap::from([(
304 "alice".to_string(),
305 vec!["10.0.0.5/32".parse::<AddressRange>().unwrap()],
306 )]);
307 let manager = AddressPoolManager::new(test_network(), user_pools).unwrap();
308
309 let addr = manager.allocate_address("alice").unwrap();
310 assert!(manager.allocate_address("alice").is_none());
311
312 manager.release_address("alice", &addr.addr());
313 assert!(manager.allocate_address("alice").is_some());
314 }
315
316 #[test]
317 fn manager_release_global_and_reallocate() {
318 let manager = AddressPoolManager::new(test_network(), HashMap::new()).unwrap();
319
320 let addr = manager.allocate_address("bob").unwrap();
321 manager.release_address("bob", &addr.addr());
322
323 let addr2 = manager.allocate_address("bob").unwrap();
324 assert_eq!(addr, addr2);
325 }
326
327 #[test]
328 fn manager_rejects_user_pool_outside_network() {
329 let user_pools = HashMap::from([(
330 "alice".to_string(),
331 vec!["192.168.1.1/32".parse::<AddressRange>().unwrap()],
332 )]);
333 let result = AddressPoolManager::new(test_network(), user_pools);
334 assert!(result.is_err());
335 }
336
337 #[test]
338 fn manager_no_user_pools() {
339 let manager = AddressPoolManager::new(test_network(), HashMap::new()).unwrap();
340
341 for expected in 2..=6u8 {
343 let addr = manager.allocate_address("anyone").unwrap();
344 assert_eq!(addr.addr(), IpAddr::from(Ipv4Addr::new(10, 0, 0, expected)));
345 }
346 assert!(manager.allocate_address("anyone").is_none());
347 }
348
349 #[test]
350 fn manager_rejects_user_pool_with_network_address() {
351 let user_pools = HashMap::from([(
352 "alice".to_string(),
353 vec!["10.0.0.0/32".parse::<AddressRange>().unwrap()],
354 )]);
355 let result = AddressPoolManager::new(test_network(), user_pools);
356 assert!(result.is_err());
357 let err = result.err().unwrap().to_string();
358 assert!(err.contains("reserved tunnel address"), "error: {err}");
359 }
360
361 #[test]
362 fn manager_rejects_user_pool_with_server_address() {
363 let user_pools = HashMap::from([(
364 "alice".to_string(),
365 vec!["10.0.0.1/32".parse::<AddressRange>().unwrap()],
366 )]);
367 let result = AddressPoolManager::new(test_network(), user_pools);
368 assert!(result.is_err());
369 let err = result.err().unwrap().to_string();
370 assert!(err.contains("reserved tunnel address"), "error: {err}");
371 }
372
373 #[test]
374 fn manager_rejects_user_pool_with_broadcast_address() {
375 let user_pools = HashMap::from([(
376 "alice".to_string(),
377 vec!["10.0.0.7/32".parse::<AddressRange>().unwrap()],
378 )]);
379 let result = AddressPoolManager::new(test_network(), user_pools);
380 assert!(result.is_err());
381 let err = result.err().unwrap().to_string();
382 assert!(err.contains("reserved tunnel address"), "error: {err}");
383 }
384
385 #[test]
386 fn manager_netmask_preserved() {
387 let manager = AddressPoolManager::new(test_network(), HashMap::new()).unwrap();
388 let addr = manager.allocate_address("bob").unwrap();
389 assert_eq!(addr.netmask(), test_network().netmask());
390 }
391}