1use core::net::IpAddr;
3
4use ipnet::{IpNet, Ipv4Net, Ipv6Net};
5use prefix_trie::PrefixSet;
6use tracing::debug;
7
8use crate::ProtoError;
9
10pub struct AccessControlSetBuilder(AccessControlSet);
12
13impl<'a> AccessControlSetBuilder {
14 pub fn new(name: &'static str) -> Self {
18 Self(AccessControlSet::new(name))
19 }
20
21 pub fn allow(mut self, allow: impl Iterator<Item = &'a IpNet>) -> Self {
27 for network in allow {
28 debug!(name = self.0.name, ?network, "appending to allow list");
29 match network {
30 IpNet::V4(network) => {
31 self.0.v4_allow.insert(*network);
32 }
33 IpNet::V6(network) => {
34 self.0.v6_allow.insert(*network);
35 }
36 }
37 }
38 self
39 }
40
41 pub fn deny(mut self, deny: impl Iterator<Item = &'a IpNet>) -> Self {
47 for network in deny {
48 debug!(name = self.0.name, ?network, "appending to deny list");
49 match network {
50 IpNet::V4(network) => {
51 self.0.v4_deny.insert(*network);
52 }
53 IpNet::V6(network) => {
54 self.0.v6_deny.insert(*network);
55 }
56 }
57 }
58 self
59 }
60
61 pub fn clear_allow(mut self) -> Self {
63 self.0.v4_allow.clear();
64 self.0.v6_allow.clear();
65 self
66 }
67
68 pub fn clear_deny(mut self) -> Self {
70 self.0.v4_deny.clear();
71 self.0.v6_deny.clear();
72 self
73 }
74
75 pub fn build(self) -> Result<AccessControlSet, ProtoError> {
80 let deny_empty = self.0.v4_deny.is_empty() && self.0.v6_deny.is_empty();
81 let allowed_count = self.0.v4_allow.iter().count() + self.0.v6_allow.iter().count();
82 if deny_empty && allowed_count != 0 {
83 return Err(format!(
84 "access control set {name:?} has {allowed_count} allowed overrides, but no denied networks to override",
85 name = self.0.name
86 ).into());
87 }
88 Ok(self.0)
89 }
90}
91
92#[derive(Clone, Debug)]
108pub struct AccessControlSet {
109 name: &'static str,
110 v4_allow: PrefixSet<Ipv4Net>,
111 v4_deny: PrefixSet<Ipv4Net>,
112 v6_allow: PrefixSet<Ipv6Net>,
113 v6_deny: PrefixSet<Ipv6Net>,
114}
115
116impl AccessControlSet {
117 fn new(name: &'static str) -> Self {
121 Self {
122 name,
123 v4_allow: PrefixSet::new(),
124 v4_deny: PrefixSet::new(),
125 v6_allow: PrefixSet::new(),
126 v6_deny: PrefixSet::new(),
127 }
128 }
129
130 pub fn empty(name: &'static str) -> Self {
132 Self::new(name)
133 }
134
135 pub fn allows_all(&self) -> bool {
139 self.v4_deny.is_empty() && self.v6_deny.is_empty()
140 }
141
142 pub fn denied(&self, ip: IpAddr) -> bool {
151 if self.allows_all() {
153 return false;
154 }
155 match ip {
156 IpAddr::V4(ip) => {
157 self.v4_allow.get_spm(&ip.into()).is_none()
158 && self.v4_deny.get_spm(&ip.into()).is_some()
159 }
160 IpAddr::V6(ip) => {
161 self.v6_allow.get_spm(&ip.into()).is_none()
162 && self.v6_deny.get_spm(&ip.into()).is_some()
163 }
164 }
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use crate::access_control::{AccessControlSet, AccessControlSetBuilder};
171
172 #[test]
173 fn access_control_set_networks_test() {
174 let acs = AccessControlSetBuilder::new("test acs")
175 .deny(
176 [
177 "10.0.0.0/8".parse().unwrap(),
178 "172.16.0.0/12".parse().unwrap(),
179 "192.168.0.0/16".parse().unwrap(),
180 "fe80::/10".parse().unwrap(),
181 ]
182 .iter(),
183 )
184 .allow(
185 [
186 "10.1.0.3/29".parse().unwrap(),
187 "192.168.1.10/32".parse().unwrap(),
188 "fe80::200/128".parse().unwrap(),
189 ]
190 .iter(),
191 )
192 .build()
193 .unwrap();
194
195 assert!(acs.denied([10, 0, 254, 254].into()));
198 assert!(!acs.denied([10, 1, 0, 0].into()));
199 assert!(!acs.denied([10, 1, 0, 3].into()));
200 assert!(!acs.denied([10, 1, 0, 7].into()));
201 assert!(acs.denied([10, 1, 0, 8].into()));
202
203 assert!(acs.denied([192, 168, 1, 1].into()));
204 assert!(!acs.denied([192, 168, 1, 10].into()));
205
206 assert!(!acs.denied([0xfe80, 0, 0, 0, 0, 0, 0, 0x200].into()));
207 assert!(acs.denied([0xfe80, 0, 0, 0, 0, 0, 0, 1].into()));
208 }
209
210 #[test]
212 fn access_control_semantics_test() {
213 struct TestCase {
214 name: &'static str,
215 in_deny: bool,
216 in_allow: bool,
217 expected_build_err: bool,
218 expected_denied: bool,
219 }
220
221 let test_cases = [
222 TestCase {
223 name: "deny=true, allow=false -> denied",
224 in_deny: true,
225 in_allow: false,
226 expected_build_err: false,
227 expected_denied: true,
228 },
229 TestCase {
230 name: "deny=false, allow=false -> allowed",
231 in_deny: false,
232 in_allow: false,
233 expected_build_err: false,
234 expected_denied: false,
235 },
236 TestCase {
237 name: "deny=true, allow=true -> allowed",
238 in_deny: true,
239 in_allow: true,
240 expected_build_err: false,
241 expected_denied: false,
242 },
243 TestCase {
244 name: "deny=false, allow=true -> allowed",
245 in_deny: false,
246 in_allow: true,
247 expected_build_err: true,
248 expected_denied: false,
249 },
250 ];
251
252 let test_v4 = [192, 0, 2, 1].into();
253 let test_v4_net = "192.0.2.0/24".parse().unwrap();
254 let test_v6 = [0x2001, 0xdb8, 0, 0, 0, 0, 0, 1].into();
255 let test_v6_net = "2001:db8::/32".parse().unwrap();
256
257 for tc in &test_cases {
258 let mut builder = AccessControlSetBuilder::new(tc.name);
259 if tc.in_deny {
260 builder = builder.deny([test_v4_net, test_v6_net].iter());
261 }
262 if tc.in_allow {
263 builder = builder.allow([test_v4_net, test_v6_net].iter());
264 }
265
266 let Ok(acs) = builder.build() else {
267 match tc.expected_build_err {
268 true => continue,
269 false => panic!("unexpected builder error"),
270 }
271 };
272 assert_eq!(
273 acs.denied(test_v4),
274 tc.expected_denied,
275 "IPv4 case '{}' failed",
276 tc.name
277 );
278 assert_eq!(
279 acs.denied(test_v6),
280 tc.expected_denied,
281 "IPv6 case '{}' failed",
282 tc.name
283 );
284 }
285 }
286
287 #[test]
288 fn allows_all_test() {
289 let empty = AccessControlSet::empty("empty");
290 assert!(empty.allows_all());
291
292 let v4_only = AccessControlSetBuilder::new("v4 only")
293 .deny(["10.0.0.0/8".parse().unwrap()].iter())
294 .build()
295 .unwrap();
296 assert!(!v4_only.allows_all());
297
298 let v6_only = AccessControlSetBuilder::new("v6 only")
299 .deny(["fe80::/10".parse().unwrap()].iter())
300 .build()
301 .unwrap();
302 assert!(!v6_only.allows_all());
303
304 let both = AccessControlSetBuilder::new("both")
305 .deny(["10.0.0.0/8".parse().unwrap(), "fe80::/10".parse().unwrap()].iter())
306 .build()
307 .unwrap();
308 assert!(!both.allows_all());
309 }
310
311 #[test]
312 fn v4_only_deny_test() {
313 let acs = AccessControlSetBuilder::new("v4 only deny")
314 .deny(["10.0.0.0/8".parse().unwrap()].iter())
315 .build()
316 .unwrap();
317
318 assert!(!acs.allows_all());
319 assert!(acs.denied([10, 0, 0, 1].into()));
320 assert!(acs.denied([10, 255, 255, 255].into()));
321 assert!(!acs.denied([11, 0, 0, 1].into()));
322 assert!(!acs.denied([0xfe80, 0, 0, 0, 0, 0, 0, 1].into()));
323 }
324
325 #[test]
326 fn v6_only_deny_test() {
327 let acs = AccessControlSetBuilder::new("v6 only deny")
328 .deny(["fe80::/10".parse().unwrap()].iter())
329 .build()
330 .unwrap();
331
332 assert!(!acs.allows_all());
333 assert!(acs.denied([0xfe80, 0, 0, 0, 0, 0, 0, 1].into()));
334 assert!(
335 acs.denied(
336 [
337 0xfebf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff
338 ]
339 .into()
340 )
341 );
342 assert!(!acs.denied([0xfec0, 0, 0, 0, 0, 0, 0, 1].into()));
343 assert!(!acs.denied([10, 0, 0, 1].into()));
344 }
345}