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!("fuchsia.net.dhcp.AddressPool contained prefix_length ({}) which cannot fit address pool defined by range_start: ({}) and range_stop: ({})", prefix_length, pool_range_start, pool_range_stop))
186 } else {
187 Ok(addresses_candidate)
188 }
189 } else {
190 Err(anyhow::format_err!("fuchsia.net.dhcp.AddressPool missing fields: {:?}", fidl))
191 }
192 }
193
194 fn try_into_fidl(self) -> Result<fidl_fuchsia_net_dhcp::AddressPool, Self::IntoError> {
195 let ManagedAddresses { mask, pool_range_start, pool_range_stop } = self;
196 Ok(fidl_fuchsia_net_dhcp::AddressPool {
197 prefix_length: Some(mask.ones()),
198 range_start: Some(pool_range_start.into_fidl()),
199 range_stop: Some(pool_range_stop.into_fidl()),
200 ..Default::default()
201 })
202 }
203}
204
205#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
207pub struct PermittedMacs(pub Vec<fidl_fuchsia_net_ext::MacAddress>);
208
209#[cfg(target_os = "fuchsia")]
210impl FidlCompatible<Vec<fidl_fuchsia_net::MacAddress>> for PermittedMacs {
211 type FromError = Never;
212 type IntoError = Never;
213
214 fn try_from_fidl(fidl: Vec<fidl_fuchsia_net::MacAddress>) -> Result<Self, Self::FromError> {
215 Ok(PermittedMacs(fidl.into_iter().map(|mac| mac.into()).collect()))
216 }
217
218 fn try_into_fidl(self) -> Result<Vec<fidl_fuchsia_net::MacAddress>, Self::IntoError> {
219 Ok(self.0.into_iter().map(|mac| mac.into()).collect())
220 }
221}
222
223#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
226pub struct StaticAssignments(pub HashMap<fidl_fuchsia_net_ext::MacAddress, Ipv4Addr>);
227
228#[cfg(target_os = "fuchsia")]
229impl FidlCompatible<Vec<fidl_fuchsia_net_dhcp::StaticAssignment>> for StaticAssignments {
230 type FromError = anyhow::Error;
231 type IntoError = Never;
232
233 fn try_from_fidl(
234 fidl: Vec<fidl_fuchsia_net_dhcp::StaticAssignment>,
235 ) -> Result<Self, Self::FromError> {
236 match fidl.into_iter().try_fold(HashMap::new(), |mut acc, assignment| {
237 if let (Some(host), Some(assigned_addr)) = (assignment.host, assignment.assigned_addr) {
238 let mac = fidl_fuchsia_net_ext::MacAddress::from(host);
239 match acc.insert(mac, Ipv4Addr::from_fidl(assigned_addr)) {
240 Some(_ip) => Err(anyhow::format_err!(
241 "fuchsia.net.dhcp.StaticAssignment contained multiple entries for {}",
242 mac
243 )),
244 None => Ok(acc),
245 }
246 } else {
247 Err(anyhow::format_err!(
248 "fuchsia.net.dhcp.StaticAssignment contained entry with missing fields: {:?}",
249 assignment
250 ))
251 }
252 }) {
253 Ok(static_assignments) => Ok(StaticAssignments(static_assignments)),
254 Err(e) => Err(e),
255 }
256 }
257
258 fn try_into_fidl(
259 self,
260 ) -> Result<Vec<fidl_fuchsia_net_dhcp::StaticAssignment>, Self::IntoError> {
261 Ok(self
262 .0
263 .into_iter()
264 .map(|(host, assigned_addr)| fidl_fuchsia_net_dhcp::StaticAssignment {
265 host: Some(host.into()),
266 assigned_addr: Some(assigned_addr.into_fidl()),
267 ..Default::default()
268 })
269 .collect())
270 }
271}
272
273#[derive(Debug, Error)]
276pub enum ConfigError {
277 #[error("io error: {}", _0)]
278 IoError(io::Error),
279 #[error("json deserialization error: {}", _0)]
280 JsonError(serde_json::Error),
281}
282
283impl From<io::Error> for ConfigError {
284 fn from(e: io::Error) -> Self {
285 ConfigError::IoError(e)
286 }
287}
288
289impl From<serde_json::Error> for ConfigError {
290 fn from(e: serde_json::Error) -> Self {
291 ConfigError::JsonError(e)
292 }
293}
294
295#[derive(Clone, Copy, Debug, PartialEq)]
298pub struct SubnetMask {
299 prefix_length: PrefixLength<Ipv4>,
301}
302
303mod serde_impls {
304 use net_types::ip::PrefixLength;
305 use serde::de::Error as _;
306 use serde::{Deserialize, Serialize};
307
308 #[derive(Serialize, Deserialize)]
312 struct SubnetMask {
313 ones: u8,
314 }
315
316 impl Serialize for super::SubnetMask {
317 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
318 where
319 S: serde::Serializer,
320 {
321 let Self { prefix_length } = self;
322 SubnetMask { ones: prefix_length.get() }.serialize(serializer)
323 }
324 }
325
326 impl<'de> Deserialize<'de> for super::SubnetMask {
327 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
328 where
329 D: serde::Deserializer<'de>,
330 {
331 let SubnetMask { ones } = Deserialize::deserialize(deserializer)?;
332 Ok(super::SubnetMask {
333 prefix_length: PrefixLength::new(ones).map_err(
334 |net_types::ip::PrefixTooLongError| {
335 D::Error::custom(format!("{ones} too long to be IPv4 prefix length"))
336 },
337 )?,
338 })
339 }
340 }
341}
342
343impl SubnetMask {
344 pub const fn new(prefix_length: PrefixLength<Ipv4>) -> Self {
346 SubnetMask { prefix_length }
347 }
348
349 pub fn octets(&self) -> [u8; 4] {
351 let Self { prefix_length } = self;
352 prefix_length.get_mask().ipv4_bytes()
353 }
354
355 fn to_u32(&self) -> u32 {
356 u32::from_be_bytes(self.octets())
357 }
358
359 pub fn ones(&self) -> u8 {
361 let Self { prefix_length } = self;
362 prefix_length.get()
363 }
364
365 pub fn apply_to(&self, target: &Ipv4Addr) -> Ipv4Addr {
367 let Self { prefix_length } = self;
368 net_types::ip::Ipv4Addr::from(*target).mask(prefix_length.get()).into()
369 }
370
371 pub fn broadcast_of(&self, target: &Ipv4Addr) -> Ipv4Addr {
373 let subnet_mask_bits = self.to_u32();
374 let target_bits = u32::from_be_bytes(target.octets());
375 Ipv4Addr::from(!subnet_mask_bits | target_bits)
376 }
377
378 pub fn subnet_size(&self) -> u32 {
380 !self.to_u32()
381 }
382}
383
384impl TryFrom<Ipv4Addr> for SubnetMask {
385 type Error = anyhow::Error;
386
387 fn try_from(mask: Ipv4Addr) -> Result<Self, Self::Error> {
388 Ok(SubnetMask {
389 prefix_length: PrefixLength::try_from_subnet_mask(net_types::ip::Ipv4Addr::from(mask))
390 .map_err(|net_types::ip::NotSubnetMaskError| {
391 anyhow::anyhow!("{mask} is not a valid subnet mask")
392 })?,
393 })
394 }
395}
396
397#[cfg(target_os = "fuchsia")]
398impl FidlCompatible<fidl_fuchsia_net::Ipv4Address> for SubnetMask {
399 type FromError = anyhow::Error;
400 type IntoError = Never;
401
402 fn try_from_fidl(fidl: fidl_fuchsia_net::Ipv4Address) -> Result<Self, Self::FromError> {
403 let addr = Ipv4Addr::from_fidl(fidl);
404 SubnetMask::try_from(addr)
405 }
406
407 fn try_into_fidl(self) -> Result<fidl_fuchsia_net::Ipv4Address, Self::IntoError> {
408 let addr = Ipv4Addr::from(self.to_u32());
409 Ok(addr.into_fidl())
410 }
411}
412
413impl From<SubnetMask> for Ipv4Addr {
414 fn from(value: SubnetMask) -> Self {
415 Self::from(value.to_u32())
416 }
417}
418
419impl From<SubnetMask> for PrefixLength<Ipv4> {
420 fn from(value: SubnetMask) -> Self {
421 let SubnetMask { prefix_length } = value;
422 prefix_length
423 }
424}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429 use crate::server::tests::{random_ipv4_generator, random_mac_generator};
430 use net_declare::{fidl_ip_v4, net_prefix_length_v4, std_ip_v4};
431
432 #[macro_export]
437 macro_rules! assert_err_with_substring {
438 ($result:expr, $substr:expr) => {{
439 match $result {
440 Err(e) => {
441 let err_str = e.to_string();
442 assert!(err_str.contains($substr), "{} not in {}", $substr, err_str)
443 }
444 Ok(v) => panic!(
445 "{} (Ok({:?})) is not an Err containing {} ({})",
446 stringify!($result),
447 v,
448 stringify!($substr),
449 $substr
450 ),
451 }
452 }};
453 }
454
455 #[test]
456 fn try_from_ipv4addr_with_consecutive_ones_returns_mask() {
457 assert_eq!(
458 SubnetMask::try_from(std_ip_v4!("255.255.255.0"))
459 .expect("failed to create /24 subnet mask"),
460 SubnetMask { prefix_length: net_prefix_length_v4!(24) }
461 );
462 assert_eq!(
463 SubnetMask::try_from(std_ip_v4!("255.255.255.255"))
464 .expect("failed to create /32 subnet mask"),
465 SubnetMask { prefix_length: net_prefix_length_v4!(32) }
466 );
467 }
468
469 #[test]
470 fn try_from_ipv4addr_with_nonconsecutive_ones_returns_err() {
471 assert!(SubnetMask::try_from(std_ip_v4!("255.255.255.1")).is_err());
472 }
473
474 #[test]
475 fn lease_length_try_from_fidl() {
476 let both = fidl_fuchsia_net_dhcp::LeaseLength {
477 default: Some(42),
478 max: Some(42),
479 ..Default::default()
480 };
481 let with_default = fidl_fuchsia_net_dhcp::LeaseLength {
482 default: Some(42),
483 max: None,
484 ..Default::default()
485 };
486 let with_max = fidl_fuchsia_net_dhcp::LeaseLength {
487 default: None,
488 max: Some(42),
489 ..Default::default()
490 };
491 let neither =
492 fidl_fuchsia_net_dhcp::LeaseLength { default: None, max: None, ..Default::default() };
493
494 assert_eq!(
495 LeaseLength::try_from_fidl(both).unwrap(),
496 LeaseLength { default_seconds: 42, max_seconds: 42 }
497 );
498 assert_eq!(
499 LeaseLength::try_from_fidl(with_default).unwrap(),
500 LeaseLength { default_seconds: 42, max_seconds: 42 }
501 );
502 assert!(LeaseLength::try_from_fidl(with_max).is_err());
503 assert!(LeaseLength::try_from_fidl(neither).is_err());
504 }
505
506 #[test]
507 fn managed_addresses_try_from_fidl() {
508 let prefix_length = 24;
509 let start_addr = fidl_ip_v4!("192.168.0.2");
510 let stop_addr = fidl_ip_v4!("192.168.0.254");
511 let correct_pool = fidl_fuchsia_net_dhcp::AddressPool {
512 prefix_length: Some(prefix_length),
513 range_start: Some(start_addr),
514 range_stop: Some(stop_addr),
515 ..Default::default()
516 };
517
518 assert_matches::assert_matches!(
519 ManagedAddresses::try_from_fidl(correct_pool),
520 Ok(ManagedAddresses {
521 mask,
522 pool_range_start,
523 pool_range_stop,
524 }) if mask.ones() == prefix_length && pool_range_start.into_fidl() == start_addr && pool_range_stop.into_fidl() == stop_addr
525 );
526
527 let bad_prefix_length_pool = fidl_fuchsia_net_dhcp::AddressPool {
528 prefix_length: Some(33),
529 range_start: Some(fidl_ip_v4!("192.168.0.2")),
530 range_stop: Some(fidl_ip_v4!("192.168.0.254")),
531 ..Default::default()
532 };
533
534 assert_err_with_substring!(
535 ManagedAddresses::try_from_fidl(bad_prefix_length_pool),
536 "from prefix_length"
537 );
538
539 let missing_fields_pool = fidl_fuchsia_net_dhcp::AddressPool {
540 prefix_length: None,
541 range_start: Some(fidl_ip_v4!("192.168.0.2")),
542 range_stop: Some(fidl_ip_v4!("192.168.0.254")),
543 ..Default::default()
544 };
545
546 assert_err_with_substring!(
547 ManagedAddresses::try_from_fidl(missing_fields_pool),
548 "missing fields"
549 );
550
551 let start_after_stop_pool = fidl_fuchsia_net_dhcp::AddressPool {
552 prefix_length: Some(24),
553 range_start: Some(fidl_ip_v4!("192.168.0.20")),
554 range_stop: Some(fidl_ip_v4!("192.168.0.10")),
555 ..Default::default()
556 };
557
558 assert_err_with_substring!(
559 ManagedAddresses::try_from_fidl(start_after_stop_pool),
560 "> range_stop"
561 );
562
563 let mask_range_too_small_pool = fidl_fuchsia_net_dhcp::AddressPool {
564 prefix_length: Some(24),
565 range_start: Some(fidl_ip_v4!("192.168.0.0")),
566 range_stop: Some(fidl_ip_v4!("192.168.1.0")),
567 ..Default::default()
568 };
569
570 assert_err_with_substring!(
571 ManagedAddresses::try_from_fidl(mask_range_too_small_pool),
572 "cannot fit address pool"
573 );
574 }
575
576 #[test]
577 fn static_assignments_try_from_fidl() {
578 use std::iter::FromIterator;
579
580 let mac = random_mac_generator().bytes();
581 let ip = random_ipv4_generator();
582 let fields_present = vec![fidl_fuchsia_net_dhcp::StaticAssignment {
583 host: Some(fidl_fuchsia_net::MacAddress { octets: mac.clone() }),
584 assigned_addr: Some(ip.into_fidl()),
585 ..Default::default()
586 }];
587 let multiple_entries = vec![
588 fidl_fuchsia_net_dhcp::StaticAssignment {
589 host: Some(fidl_fuchsia_net::MacAddress { octets: mac.clone() }),
590 assigned_addr: Some(ip.into_fidl()),
591 ..Default::default()
592 },
593 fidl_fuchsia_net_dhcp::StaticAssignment {
594 host: Some(fidl_fuchsia_net::MacAddress { octets: mac.clone() }),
595 assigned_addr: Some(random_ipv4_generator().into_fidl()),
596 ..Default::default()
597 },
598 ];
599 let fields_missing = vec![fidl_fuchsia_net_dhcp::StaticAssignment {
600 host: None,
601 assigned_addr: None,
602 ..Default::default()
603 }];
604
605 assert_eq!(
606 StaticAssignments::try_from_fidl(fields_present).unwrap(),
607 StaticAssignments(HashMap::from_iter(
608 vec![(fidl_fuchsia_net_ext::MacAddress { octets: mac }, ip)].into_iter()
609 ))
610 );
611 assert!(StaticAssignments::try_from_fidl(multiple_entries).is_err());
612 assert!(StaticAssignments::try_from_fidl(fields_missing).is_err());
613 }
614}