1#[cfg(target_os = "fuchsia")]
6use crate::protocol::{FidlCompatible, FromFidlExt, IntoFidlExt};
7
8#[cfg(target_os = "fuchsia")]
9use anyhow::Context;
10
11use net_types::ip::{IpAddress as _, Ipv4, PrefixLength};
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::io;
15use std::net::Ipv4Addr;
16use std::num::TryFromIntError;
17use thiserror::Error;
18
19#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
21pub struct ServerParameters {
22 pub server_ips: Vec<Ipv4Addr>,
24 pub lease_length: LeaseLength,
26 pub managed_addrs: ManagedAddresses,
29 pub permitted_macs: PermittedMacs,
32 pub static_assignments: StaticAssignments,
35 pub arp_probe: bool,
38 pub bound_device_names: Vec<String>,
43}
44
45impl ServerParameters {
46 pub fn is_valid(&self) -> bool {
47 let Self {
48 server_ips,
49 lease_length: crate::configuration::LeaseLength { default_seconds, max_seconds },
50 managed_addrs:
51 crate::configuration::ManagedAddresses { mask: _, pool_range_start, pool_range_stop },
52 permitted_macs: _,
53 static_assignments: _,
54 arp_probe: _,
55 bound_device_names: _,
56 } = self;
57 if server_ips.is_empty() {
58 return false;
59 }
60 if [pool_range_start, pool_range_stop]
61 .into_iter()
62 .chain(server_ips.iter())
63 .any(std::net::Ipv4Addr::is_unspecified)
64 {
65 return false;
66 }
67 if *default_seconds == 0 {
68 return false;
69 }
70 if *max_seconds == 0 {
71 return false;
72 }
73 true
74 }
75}
76
77#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
80pub struct LeaseLength {
81 pub default_seconds: u32,
83 pub max_seconds: u32,
85}
86
87#[cfg(target_os = "fuchsia")]
88impl FidlCompatible<fidl_fuchsia_net_dhcp::LeaseLength> for LeaseLength {
89 type FromError = anyhow::Error;
90 type IntoError = !;
91
92 fn try_from_fidl(fidl: fidl_fuchsia_net_dhcp::LeaseLength) -> Result<Self, Self::FromError> {
93 if let fidl_fuchsia_net_dhcp::LeaseLength { default: Some(default_seconds), max, .. } = fidl
94 {
95 Ok(LeaseLength {
96 default_seconds,
97 max_seconds: max.unwrap_or(default_seconds),
99 })
100 } else {
101 Err(anyhow::format_err!(
102 "fuchsia.net.dhcp.LeaseLength missing required field: {:?}",
103 fidl
104 ))
105 }
106 }
107
108 fn try_into_fidl(self) -> Result<fidl_fuchsia_net_dhcp::LeaseLength, Self::IntoError> {
109 let LeaseLength { default_seconds, max_seconds } = self;
110 Ok(fidl_fuchsia_net_dhcp::LeaseLength {
111 default: Some(default_seconds),
112 max: Some(max_seconds),
113 ..Default::default()
114 })
115 }
116}
117
118#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Serialize)]
120pub struct ManagedAddresses {
121 pub mask: SubnetMask,
123 pub pool_range_start: Ipv4Addr,
125 pub pool_range_stop: Ipv4Addr,
127}
128
129impl ManagedAddresses {
130 fn pool_range_inner(&self) -> std::ops::Range<u32> {
131 let Self { mask: _, pool_range_start, pool_range_stop } = *self;
132 pool_range_start.into()..pool_range_stop.into()
133 }
134 pub fn pool_range(&self) -> impl Iterator<Item = Ipv4Addr> {
137 self.pool_range_inner().map(Into::into)
138 }
139
140 pub fn pool_range_size(&self) -> Result<u32, TryFromIntError> {
143 self.pool_range_inner().len().try_into()
144 }
145}
146
147#[cfg(target_os = "fuchsia")]
148impl FidlCompatible<fidl_fuchsia_net_dhcp::AddressPool> for ManagedAddresses {
149 type FromError = anyhow::Error;
150 type IntoError = !;
151
152 fn try_from_fidl(fidl: fidl_fuchsia_net_dhcp::AddressPool) -> Result<Self, Self::FromError> {
153 if let fidl_fuchsia_net_dhcp::AddressPool {
154 prefix_length: Some(prefix_length),
155 range_start: Some(pool_range_start),
156 range_stop: Some(pool_range_stop),
157 ..
158 } = fidl
159 {
160 let mask = PrefixLength::new(prefix_length).map(SubnetMask::new).map_err(
161 |net_types::ip::PrefixTooLongError| {
162 anyhow::format_err!(
163 "failed to create subnet mask from prefix_length={}",
164 prefix_length
165 )
166 },
167 )?;
168 let pool_range_start = Ipv4Addr::from_fidl(pool_range_start);
169 let pool_range_stop = Ipv4Addr::from_fidl(pool_range_stop);
170 let addresses_candidate = Self { mask, pool_range_start, pool_range_stop };
171 if pool_range_start > pool_range_stop {
172 return Err(anyhow::format_err!(
173 "fuchsia.net.dhcp.AddressPool contained range_start ({}) > range_stop ({})",
174 pool_range_start,
175 pool_range_stop
176 ));
177 }
178 let pool_range_size = addresses_candidate.pool_range_size().with_context(|| {
179 format!("failed to determine address pool size for range_start ({}) and range_stop ({})", pool_range_start, pool_range_stop)
180 })?;
181 if pool_range_size > mask.subnet_size() {
182 Err(anyhow::format_err!(
183 "fuchsia.net.dhcp.AddressPool contained prefix_length ({}) which cannot fit address pool defined by range_start: ({}) and range_stop: ({})",
184 prefix_length,
185 pool_range_start,
186 pool_range_stop
187 ))
188 } else {
189 Ok(addresses_candidate)
190 }
191 } else {
192 Err(anyhow::format_err!("fuchsia.net.dhcp.AddressPool missing fields: {:?}", fidl))
193 }
194 }
195
196 fn try_into_fidl(self) -> Result<fidl_fuchsia_net_dhcp::AddressPool, Self::IntoError> {
197 let ManagedAddresses { mask, pool_range_start, pool_range_stop } = self;
198 Ok(fidl_fuchsia_net_dhcp::AddressPool {
199 prefix_length: Some(mask.ones()),
200 range_start: Some(pool_range_start.into_fidl()),
201 range_stop: Some(pool_range_stop.into_fidl()),
202 ..Default::default()
203 })
204 }
205}
206
207#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
209pub struct PermittedMacs(pub Vec<fidl_fuchsia_net_ext::MacAddress>);
210
211#[cfg(target_os = "fuchsia")]
212impl FidlCompatible<Vec<fidl_fuchsia_net::MacAddress>> for PermittedMacs {
213 type FromError = !;
214 type IntoError = !;
215
216 fn try_from_fidl(fidl: Vec<fidl_fuchsia_net::MacAddress>) -> Result<Self, Self::FromError> {
217 Ok(PermittedMacs(fidl.into_iter().map(|mac| mac.into()).collect()))
218 }
219
220 fn try_into_fidl(self) -> Result<Vec<fidl_fuchsia_net::MacAddress>, Self::IntoError> {
221 Ok(self.0.into_iter().map(|mac| mac.into()).collect())
222 }
223}
224
225#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
228pub struct StaticAssignments(pub HashMap<fidl_fuchsia_net_ext::MacAddress, Ipv4Addr>);
229
230#[cfg(target_os = "fuchsia")]
231impl FidlCompatible<Vec<fidl_fuchsia_net_dhcp::StaticAssignment>> for StaticAssignments {
232 type FromError = anyhow::Error;
233 type IntoError = !;
234
235 fn try_from_fidl(
236 fidl: Vec<fidl_fuchsia_net_dhcp::StaticAssignment>,
237 ) -> Result<Self, Self::FromError> {
238 match fidl.into_iter().try_fold(HashMap::new(), |mut acc, assignment| {
239 if let (Some(host), Some(assigned_addr)) = (assignment.host, assignment.assigned_addr) {
240 let mac = fidl_fuchsia_net_ext::MacAddress::from(host);
241 match acc.insert(mac, Ipv4Addr::from_fidl(assigned_addr)) {
242 Some(_ip) => Err(anyhow::format_err!(
243 "fuchsia.net.dhcp.StaticAssignment contained multiple entries for {}",
244 mac
245 )),
246 None => Ok(acc),
247 }
248 } else {
249 Err(anyhow::format_err!(
250 "fuchsia.net.dhcp.StaticAssignment contained entry with missing fields: {:?}",
251 assignment
252 ))
253 }
254 }) {
255 Ok(static_assignments) => Ok(StaticAssignments(static_assignments)),
256 Err(e) => Err(e),
257 }
258 }
259
260 fn try_into_fidl(
261 self,
262 ) -> Result<Vec<fidl_fuchsia_net_dhcp::StaticAssignment>, Self::IntoError> {
263 Ok(self
264 .0
265 .into_iter()
266 .map(|(host, assigned_addr)| fidl_fuchsia_net_dhcp::StaticAssignment {
267 host: Some(host.into()),
268 assigned_addr: Some(assigned_addr.into_fidl()),
269 ..Default::default()
270 })
271 .collect())
272 }
273}
274
275#[derive(Debug, Error)]
278pub enum ConfigError {
279 #[error("io error: {}", _0)]
280 IoError(io::Error),
281 #[error("json deserialization error: {}", _0)]
282 JsonError(serde_json::Error),
283}
284
285impl From<io::Error> for ConfigError {
286 fn from(e: io::Error) -> Self {
287 ConfigError::IoError(e)
288 }
289}
290
291impl From<serde_json::Error> for ConfigError {
292 fn from(e: serde_json::Error) -> Self {
293 ConfigError::JsonError(e)
294 }
295}
296
297#[derive(Clone, Copy, Debug, PartialEq)]
300pub struct SubnetMask {
301 prefix_length: PrefixLength<Ipv4>,
303}
304
305mod serde_impls {
306 use net_types::ip::PrefixLength;
307 use serde::de::Error as _;
308 use serde::{Deserialize, Serialize};
309
310 #[derive(Serialize, Deserialize)]
314 struct SubnetMask {
315 ones: u8,
316 }
317
318 impl Serialize for super::SubnetMask {
319 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
320 where
321 S: serde::Serializer,
322 {
323 let Self { prefix_length } = self;
324 SubnetMask { ones: prefix_length.get() }.serialize(serializer)
325 }
326 }
327
328 impl<'de> Deserialize<'de> for super::SubnetMask {
329 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
330 where
331 D: serde::Deserializer<'de>,
332 {
333 let SubnetMask { ones } = Deserialize::deserialize(deserializer)?;
334 Ok(super::SubnetMask {
335 prefix_length: PrefixLength::new(ones).map_err(
336 |net_types::ip::PrefixTooLongError| {
337 D::Error::custom(format!("{ones} too long to be IPv4 prefix length"))
338 },
339 )?,
340 })
341 }
342 }
343}
344
345impl SubnetMask {
346 pub const fn new(prefix_length: PrefixLength<Ipv4>) -> Self {
348 SubnetMask { prefix_length }
349 }
350
351 pub fn octets(&self) -> [u8; 4] {
353 let Self { prefix_length } = self;
354 prefix_length.get_mask().ipv4_bytes()
355 }
356
357 fn to_u32(&self) -> u32 {
358 u32::from_be_bytes(self.octets())
359 }
360
361 pub fn ones(&self) -> u8 {
363 let Self { prefix_length } = self;
364 prefix_length.get()
365 }
366
367 pub fn apply_to(&self, target: &Ipv4Addr) -> Ipv4Addr {
369 let Self { prefix_length } = self;
370 net_types::ip::Ipv4Addr::from(*target).mask(prefix_length.get()).into()
371 }
372
373 pub fn broadcast_of(&self, target: &Ipv4Addr) -> Ipv4Addr {
375 let subnet_mask_bits = self.to_u32();
376 let target_bits = u32::from_be_bytes(target.octets());
377 Ipv4Addr::from(!subnet_mask_bits | target_bits)
378 }
379
380 pub fn subnet_size(&self) -> u32 {
382 !self.to_u32()
383 }
384}
385
386impl TryFrom<Ipv4Addr> for SubnetMask {
387 type Error = anyhow::Error;
388
389 fn try_from(mask: Ipv4Addr) -> Result<Self, Self::Error> {
390 Ok(SubnetMask {
391 prefix_length: PrefixLength::try_from_subnet_mask(net_types::ip::Ipv4Addr::from(mask))
392 .map_err(|net_types::ip::NotSubnetMaskError| {
393 anyhow::anyhow!("{mask} is not a valid subnet mask")
394 })?,
395 })
396 }
397}
398
399#[cfg(target_os = "fuchsia")]
400impl FidlCompatible<fidl_fuchsia_net::Ipv4Address> for SubnetMask {
401 type FromError = anyhow::Error;
402 type IntoError = !;
403
404 fn try_from_fidl(fidl: fidl_fuchsia_net::Ipv4Address) -> Result<Self, Self::FromError> {
405 let addr = Ipv4Addr::from_fidl(fidl);
406 SubnetMask::try_from(addr)
407 }
408
409 fn try_into_fidl(self) -> Result<fidl_fuchsia_net::Ipv4Address, Self::IntoError> {
410 let addr = Ipv4Addr::from(self.to_u32());
411 Ok(addr.into_fidl())
412 }
413}
414
415impl From<SubnetMask> for Ipv4Addr {
416 fn from(value: SubnetMask) -> Self {
417 Self::from(value.to_u32())
418 }
419}
420
421impl From<SubnetMask> for PrefixLength<Ipv4> {
422 fn from(value: SubnetMask) -> Self {
423 let SubnetMask { prefix_length } = value;
424 prefix_length
425 }
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431 use crate::server::tests::{random_ipv4_generator, random_mac_generator};
432 use net_declare::{fidl_ip_v4, net_prefix_length_v4, std_ip_v4};
433
434 #[macro_export]
439 macro_rules! assert_err_with_substring {
440 ($result:expr, $substr:expr) => {{
441 match $result {
442 Err(e) => {
443 let err_str = e.to_string();
444 assert!(err_str.contains($substr), "{} not in {}", $substr, err_str)
445 }
446 Ok(v) => panic!(
447 "{} (Ok({:?})) is not an Err containing {} ({})",
448 stringify!($result),
449 v,
450 stringify!($substr),
451 $substr
452 ),
453 }
454 }};
455 }
456
457 #[test]
458 fn try_from_ipv4addr_with_consecutive_ones_returns_mask() {
459 assert_eq!(
460 SubnetMask::try_from(std_ip_v4!("255.255.255.0"))
461 .expect("failed to create /24 subnet mask"),
462 SubnetMask { prefix_length: net_prefix_length_v4!(24) }
463 );
464 assert_eq!(
465 SubnetMask::try_from(std_ip_v4!("255.255.255.255"))
466 .expect("failed to create /32 subnet mask"),
467 SubnetMask { prefix_length: net_prefix_length_v4!(32) }
468 );
469 }
470
471 #[test]
472 fn try_from_ipv4addr_with_nonconsecutive_ones_returns_err() {
473 assert!(SubnetMask::try_from(std_ip_v4!("255.255.255.1")).is_err());
474 }
475
476 #[test]
477 fn lease_length_try_from_fidl() {
478 let both = fidl_fuchsia_net_dhcp::LeaseLength {
479 default: Some(42),
480 max: Some(42),
481 ..Default::default()
482 };
483 let with_default = fidl_fuchsia_net_dhcp::LeaseLength {
484 default: Some(42),
485 max: None,
486 ..Default::default()
487 };
488 let with_max = fidl_fuchsia_net_dhcp::LeaseLength {
489 default: None,
490 max: Some(42),
491 ..Default::default()
492 };
493 let neither =
494 fidl_fuchsia_net_dhcp::LeaseLength { default: None, max: None, ..Default::default() };
495
496 assert_eq!(
497 LeaseLength::try_from_fidl(both).unwrap(),
498 LeaseLength { default_seconds: 42, max_seconds: 42 }
499 );
500 assert_eq!(
501 LeaseLength::try_from_fidl(with_default).unwrap(),
502 LeaseLength { default_seconds: 42, max_seconds: 42 }
503 );
504 assert!(LeaseLength::try_from_fidl(with_max).is_err());
505 assert!(LeaseLength::try_from_fidl(neither).is_err());
506 }
507
508 #[test]
509 fn managed_addresses_try_from_fidl() {
510 let prefix_length = 24;
511 let start_addr = fidl_ip_v4!("192.168.0.2");
512 let stop_addr = fidl_ip_v4!("192.168.0.254");
513 let correct_pool = fidl_fuchsia_net_dhcp::AddressPool {
514 prefix_length: Some(prefix_length),
515 range_start: Some(start_addr),
516 range_stop: Some(stop_addr),
517 ..Default::default()
518 };
519
520 assert_matches::assert_matches!(
521 ManagedAddresses::try_from_fidl(correct_pool),
522 Ok(ManagedAddresses {
523 mask,
524 pool_range_start,
525 pool_range_stop,
526 }) if mask.ones() == prefix_length && pool_range_start.into_fidl() == start_addr && pool_range_stop.into_fidl() == stop_addr
527 );
528
529 let bad_prefix_length_pool = fidl_fuchsia_net_dhcp::AddressPool {
530 prefix_length: Some(33),
531 range_start: Some(fidl_ip_v4!("192.168.0.2")),
532 range_stop: Some(fidl_ip_v4!("192.168.0.254")),
533 ..Default::default()
534 };
535
536 assert_err_with_substring!(
537 ManagedAddresses::try_from_fidl(bad_prefix_length_pool),
538 "from prefix_length"
539 );
540
541 let missing_fields_pool = fidl_fuchsia_net_dhcp::AddressPool {
542 prefix_length: None,
543 range_start: Some(fidl_ip_v4!("192.168.0.2")),
544 range_stop: Some(fidl_ip_v4!("192.168.0.254")),
545 ..Default::default()
546 };
547
548 assert_err_with_substring!(
549 ManagedAddresses::try_from_fidl(missing_fields_pool),
550 "missing fields"
551 );
552
553 let start_after_stop_pool = fidl_fuchsia_net_dhcp::AddressPool {
554 prefix_length: Some(24),
555 range_start: Some(fidl_ip_v4!("192.168.0.20")),
556 range_stop: Some(fidl_ip_v4!("192.168.0.10")),
557 ..Default::default()
558 };
559
560 assert_err_with_substring!(
561 ManagedAddresses::try_from_fidl(start_after_stop_pool),
562 "> range_stop"
563 );
564
565 let mask_range_too_small_pool = fidl_fuchsia_net_dhcp::AddressPool {
566 prefix_length: Some(24),
567 range_start: Some(fidl_ip_v4!("192.168.0.0")),
568 range_stop: Some(fidl_ip_v4!("192.168.1.0")),
569 ..Default::default()
570 };
571
572 assert_err_with_substring!(
573 ManagedAddresses::try_from_fidl(mask_range_too_small_pool),
574 "cannot fit address pool"
575 );
576 }
577
578 #[test]
579 fn static_assignments_try_from_fidl() {
580 use std::iter::FromIterator;
581
582 let mac = random_mac_generator().bytes();
583 let ip = random_ipv4_generator();
584 let fields_present = vec![fidl_fuchsia_net_dhcp::StaticAssignment {
585 host: Some(fidl_fuchsia_net::MacAddress { octets: mac.clone() }),
586 assigned_addr: Some(ip.into_fidl()),
587 ..Default::default()
588 }];
589 let multiple_entries = vec![
590 fidl_fuchsia_net_dhcp::StaticAssignment {
591 host: Some(fidl_fuchsia_net::MacAddress { octets: mac.clone() }),
592 assigned_addr: Some(ip.into_fidl()),
593 ..Default::default()
594 },
595 fidl_fuchsia_net_dhcp::StaticAssignment {
596 host: Some(fidl_fuchsia_net::MacAddress { octets: mac.clone() }),
597 assigned_addr: Some(random_ipv4_generator().into_fidl()),
598 ..Default::default()
599 },
600 ];
601 let fields_missing = vec![fidl_fuchsia_net_dhcp::StaticAssignment {
602 host: None,
603 assigned_addr: None,
604 ..Default::default()
605 }];
606
607 assert_eq!(
608 StaticAssignments::try_from_fidl(fields_present).unwrap(),
609 StaticAssignments(HashMap::from_iter(
610 vec![(fidl_fuchsia_net_ext::MacAddress { octets: mac }, ip)].into_iter()
611 ))
612 );
613 assert!(StaticAssignments::try_from_fidl(multiple_entries).is_err());
614 assert!(StaticAssignments::try_from_fidl(fields_missing).is_err());
615 }
616}