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