1use std::cell::RefCell;
6use std::num::NonZeroU64;
7use std::sync::Arc;
8
9use dhcp_client_core::inspect::Counters;
10use diagnostics_traits::Inspector;
11use fidl::endpoints;
12use fidl_fuchsia_net_dhcp::{
13 self as fdhcp, ClientExitReason, ClientRequestStream, ClientWatchConfigurationResponse,
14 ConfigurationToRequest, NewClientParams,
15};
16use fidl_fuchsia_net_ext::IntoExt as _;
17use futures::channel::mpsc;
18use futures::{StreamExt, TryStreamExt as _};
19use indexmap::IndexSet;
20use net_types::ip::{Ipv4, Ipv4Addr, PrefixLength};
21use net_types::{SpecifiedAddr, Witness as _};
22use rand::SeedableRng as _;
23use {
24 fidl_fuchsia_net as fnet, fidl_fuchsia_net_interfaces as fnet_interfaces,
25 fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin,
26 fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext, fuchsia_async as fasync,
27};
28
29use crate::inspect::{Inspect, LeaseChangeInspect, LeaseInspectProperties, StateInspect};
30
31#[derive(thiserror::Error, Debug)]
32pub(crate) enum Error {
33 #[error("DHCP client exiting: {0:?}")]
34 Exit(ClientExitReason),
35
36 #[error("error observed by DHCP client core: {0:?}")]
37 Core(dhcp_client_core::client::Error),
38
39 #[error("fidl error: {0}")]
40 Fidl(fidl::Error),
41}
42
43impl Error {
44 fn from_core(core_error: dhcp_client_core::client::Error) -> Self {
45 match core_error {
46 dhcp_client_core::client::Error::Socket(socket_error) => match socket_error {
47 dhcp_client_core::deps::SocketError::NoInterface
48 | dhcp_client_core::deps::SocketError::UnsupportedHardwareType => {
49 Self::Exit(ClientExitReason::InvalidInterface)
50 }
51 dhcp_client_core::deps::SocketError::FailedToOpen(e) => {
52 log::error!("error while trying to open socket: {:?}", e);
53 Self::Exit(ClientExitReason::UnableToOpenSocket)
54 }
55 dhcp_client_core::deps::SocketError::HostUnreachable
56 | dhcp_client_core::deps::SocketError::Other(_) => {
57 Self::Core(dhcp_client_core::client::Error::Socket(socket_error))
58 }
59 dhcp_client_core::deps::SocketError::NetworkUnreachable => {
60 Self::Exit(ClientExitReason::NetworkUnreachable)
61 }
62 },
63 dhcp_client_core::client::Error::AddressEventReceiverEnded => {
64 Self::Exit(ClientExitReason::AddressStateProviderError)
65 }
66 }
67 }
68}
69
70pub(crate) async fn serve_client(
71 mac: net_types::ethernet::Mac,
72 interface_id: NonZeroU64,
73 provider: &crate::packetsocket::PacketSocketProviderImpl,
74 udp_socket_provider: &impl dhcp_client_core::deps::UdpSocketProvider,
75 params: NewClientParams,
76 requests: ClientRequestStream,
77 inspect_root: &fuchsia_inspect::Node,
78) -> Result<(), Error> {
79 let (stop_sender, stop_receiver) = mpsc::unbounded();
80 let stop_sender = &stop_sender;
81 let debug_log_prefix = dhcp_client_core::client::DebugLogPrefix { interface_id };
82 let inspect = Arc::new(Inspect::new());
83 let client = RefCell::new(Client::new(
84 mac,
85 interface_id,
86 params,
87 rand::rngs::StdRng::seed_from_u64(rand::random()),
88 stop_receiver,
89 debug_log_prefix,
90 )?);
91 let counters = Arc::new(Counters::default());
92 let _node = inspect_root.create_lazy_child(interface_id.get().to_string(), {
93 let counters = counters.clone();
94 let inspect = inspect.clone();
95 move || {
96 let inspector = fuchsia_inspect::Inspector::default();
97 {
98 let mut inspector =
99 diagnostics_traits::FuchsiaInspector::<'_, ()>::new(inspector.root());
100 inspector.record_uint("InterfaceId", interface_id.get());
101 inspect.record(&mut inspector);
102 inspector.record_child("Counters", |inspector| {
103 counters.record(inspector);
104 });
105 }
106 Box::pin(futures::future::ready(Ok(inspector)))
107 }
108 });
109
110 let counters = counters.as_ref();
111 let inspect = inspect.as_ref();
112 requests
113 .map_err(Error::Fidl)
114 .try_for_each_concurrent(None, |request| {
115 let client = &client;
116 async move {
117 match request {
118 fidl_fuchsia_net_dhcp::ClientRequest::WatchConfiguration { responder } => {
119 let mut client = client.try_borrow_mut().map_err(|_| {
120 Error::Exit(ClientExitReason::WatchConfigurationAlreadyPending)
121 })?;
122 responder
123 .send(
124 client
125 .watch_configuration(
126 provider,
127 udp_socket_provider,
128 counters,
129 inspect,
130 )
131 .await?,
132 )
133 .map_err(Error::Fidl)?;
134 Ok(())
135 }
136 fidl_fuchsia_net_dhcp::ClientRequest::Shutdown { control_handle: _ } => {
137 match stop_sender.unbounded_send(()) {
138 Ok(()) => stop_sender.close_channel(),
139 Err(try_send_error) => {
140 if try_send_error.is_disconnected() {
142 log::warn!(
143 "{debug_log_prefix} tried to send shutdown request on \
144 already-closed channel to client core"
145 );
146 } else {
147 log::error!(
148 "{debug_log_prefix} error while sending shutdown request \
149 to client core: {:?}",
150 try_send_error
151 );
152 }
153 }
154 }
155 Ok(())
156 }
157 }
158 }
159 })
160 .await
161}
162
163struct Clock;
164
165impl dhcp_client_core::deps::Clock for Clock {
166 type Instant = fasync::MonotonicInstant;
167
168 fn now(&self) -> Self::Instant {
169 fasync::MonotonicInstant::now()
170 }
171
172 async fn wait_until(&self, time: Self::Instant) {
173 fasync::Timer::new(time).await
174 }
175}
176
177struct Client {
179 config: dhcp_client_core::client::ClientConfig,
180 core: dhcp_client_core::client::State<fasync::MonotonicInstant>,
181 rng: rand::rngs::StdRng,
182 stop_receiver: mpsc::UnboundedReceiver<()>,
183 current_lease: Option<Lease>,
184 interface_id: NonZeroU64,
185}
186
187struct Lease {
188 address_state_provider: fnet_interfaces_admin::AddressStateProviderProxy,
189 assignment_state_stream: futures::stream::BoxStream<
195 'static,
196 Result<
197 fnet_interfaces::AddressAssignmentState,
198 fnet_interfaces_ext::admin::AddressStateProviderError,
199 >,
200 >,
201 ip_address: SpecifiedAddr<net_types::ip::Ipv4Addr>,
202}
203
204impl Client {
205 fn new(
206 mac: net_types::ethernet::Mac,
207 interface_id: NonZeroU64,
208 NewClientParams { configuration_to_request, request_ip_address, .. }: NewClientParams,
209 rng: rand::rngs::StdRng,
210 stop_receiver: mpsc::UnboundedReceiver<()>,
211 debug_log_prefix: dhcp_client_core::client::DebugLogPrefix,
212 ) -> Result<Self, Error> {
213 if !request_ip_address.unwrap_or(false) {
214 log::error!(
215 "{debug_log_prefix} client creation failed: \
216 DHCPINFORM is unimplemented"
217 );
218 return Err(Error::Exit(ClientExitReason::InvalidParams));
219 }
220 let ConfigurationToRequest { routers, dns_servers, .. } =
221 configuration_to_request.unwrap_or_else(ConfigurationToRequest::default);
222
223 let config = dhcp_client_core::client::ClientConfig {
224 client_hardware_address: mac,
225 client_identifier: None,
226 requested_parameters: std::iter::once((
227 dhcp_protocol::OptionCode::SubnetMask,
228 dhcp_client_core::parse::OptionRequested::Required,
229 ))
230 .chain(routers.unwrap_or(false).then_some((
231 dhcp_protocol::OptionCode::Router,
232 dhcp_client_core::parse::OptionRequested::Optional,
233 )))
234 .chain(dns_servers.unwrap_or(false).then_some((
235 dhcp_protocol::OptionCode::DomainNameServer,
236 dhcp_client_core::parse::OptionRequested::Optional,
237 )))
238 .collect::<dhcp_client_core::parse::OptionCodeMap<_>>(),
239 preferred_lease_time_secs: None,
240 requested_ip_address: None,
241 debug_log_prefix,
242 };
243 Ok(Self {
244 core: dhcp_client_core::client::State::default(),
245 rng,
246 config,
247 stop_receiver,
248 current_lease: None,
249 interface_id,
250 })
251 }
252
253 async fn handle_newly_acquired_lease(
254 &mut self,
255 dhcp_client_core::client::NewlyAcquiredLease {
256 ip_address,
257 start_time,
258 lease_time,
259 parameters,
260 }: dhcp_client_core::client::NewlyAcquiredLease<fasync::MonotonicInstant>,
261 ) -> Result<(ClientWatchConfigurationResponse, LeaseChangeInspect), Error> {
262 let Self {
263 core: _,
264 rng: _,
265 config: dhcp_client_core::client::ClientConfig { debug_log_prefix, .. },
266 stop_receiver: _,
267 current_lease,
268 interface_id: _,
269 } = self;
270
271 let mut dns_servers: Option<IndexSet<_>> = None;
274 let mut routers: Option<IndexSet<_>> = None;
275 let mut prefix_len: Option<PrefixLength<Ipv4>> = None;
276 let mut unrequested_options = Vec::new();
277
278 for option in parameters {
279 match option {
280 dhcp_protocol::DhcpOption::SubnetMask(len) => {
281 let previous_prefix_len = prefix_len.replace(len);
282 if let Some(prev) = previous_prefix_len {
283 log::warn!("expected previous_prefix_len to be None, got {prev:?}");
284 }
285 }
286 dhcp_protocol::DhcpOption::DomainNameServer(list) => {
287 dns_servers.get_or_insert_default().extend(list);
290 }
291 dhcp_protocol::DhcpOption::Router(list) => {
292 routers.get_or_insert_default().extend(list);
295 }
296 _ => {
297 unrequested_options.push(option);
298 }
299 }
300 }
301
302 if !unrequested_options.is_empty() {
303 log::warn!(
304 "{debug_log_prefix} Received options from core that we didn't ask for: {:#?}",
305 unrequested_options
306 );
307 }
308
309 let prefix_len = prefix_len
310 .expect(
311 "subnet mask should be present \
312 because it was specified to core as required",
313 )
314 .get();
315
316 let (asp_proxy, asp_server_end) =
317 endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
318
319 let previous_lease = current_lease.replace(Lease {
320 address_state_provider: asp_proxy.clone(),
321 assignment_state_stream: fnet_interfaces_ext::admin::assignment_state_stream(asp_proxy)
322 .boxed(),
323 ip_address,
324 });
325
326 if let Some(previous_lease) = previous_lease {
327 self.remove_address_for_lease(previous_lease).await?;
328 }
329
330 let lease_inspect_properties = LeaseInspectProperties {
331 ip_address,
332 lease_length: lease_time.into(),
333 dns_server_count: dns_servers.as_ref().map(|list| list.len()).unwrap_or(0),
334 routers_count: routers.as_ref().map(|list| list.len()).unwrap_or(0),
335 };
336
337 Ok((
338 ClientWatchConfigurationResponse {
339 address: Some(fdhcp::Address {
340 address: Some(fnet::Ipv4AddressWithPrefix {
341 addr: ip_address.get().into_ext(),
342 prefix_len,
343 }),
344 address_parameters: Some(fnet_interfaces_admin::AddressParameters {
345 initial_properties: Some(fnet_interfaces_admin::AddressProperties {
346 preferred_lifetime_info: None,
347 valid_lifetime_end: Some(
348 zx::MonotonicInstant::from(start_time + lease_time.into())
349 .into_nanos(),
350 ),
351 ..Default::default()
352 }),
353 add_subnet_route: Some(true),
354 perform_dad: Some(true),
355 ..Default::default()
356 }),
357 address_state_provider: Some(asp_server_end),
358 ..Default::default()
359 }),
360 dns_servers: dns_servers.map(into_fidl_list),
361 routers: routers.map(into_fidl_list),
362 ..Default::default()
363 },
364 LeaseChangeInspect::LeaseAdded {
365 start_time,
366 prefix_len,
367 properties: lease_inspect_properties,
368 },
369 ))
370 }
371
372 async fn handle_lease_renewal(
373 &mut self,
374 dhcp_client_core::client::LeaseRenewal {
375 start_time,
376 lease_time,
377 parameters,
378 }: dhcp_client_core::client::LeaseRenewal<fasync::MonotonicInstant>,
379 ) -> Result<(ClientWatchConfigurationResponse, LeaseChangeInspect), Error> {
380 let Self {
381 core: _,
382 rng: _,
383 config: dhcp_client_core::client::ClientConfig { debug_log_prefix, .. },
384 stop_receiver: _,
385 current_lease,
386 interface_id: _,
387 } = self;
388
389 let mut dns_servers: Option<IndexSet<_>> = None;
392 let mut routers: Option<IndexSet<_>> = None;
393 let mut unrequested_options = Vec::new();
394
395 for option in parameters {
396 match option {
397 dhcp_protocol::DhcpOption::SubnetMask(len) => {
398 log::info!(
399 "{debug_log_prefix} ignoring prefix length={:?} for renewed lease",
400 len
401 );
402 }
403 dhcp_protocol::DhcpOption::DomainNameServer(list) => {
404 dns_servers.get_or_insert_default().extend(list);
407 }
408 dhcp_protocol::DhcpOption::Router(list) => {
409 routers.get_or_insert_default().extend(list);
412 }
413 option => {
414 unrequested_options.push(option);
415 }
416 }
417 }
418
419 if !unrequested_options.is_empty() {
420 log::warn!(
421 "{debug_log_prefix} Received options from core that we didn't ask for: {:#?}",
422 unrequested_options
423 );
424 }
425
426 let Lease { address_state_provider, assignment_state_stream: _, ip_address } =
427 current_lease.as_mut().expect("should have current lease if we're handling a renewal");
428
429 address_state_provider
430 .update_address_properties(&fnet_interfaces_admin::AddressProperties {
431 preferred_lifetime_info: None,
432 valid_lifetime_end: Some(
433 zx::MonotonicInstant::from(start_time + lease_time.into()).into_nanos(),
434 ),
435 ..Default::default()
436 })
437 .await
438 .map_err(Error::Fidl)?;
439
440 let lease_inspect_properties = LeaseInspectProperties {
441 ip_address: *ip_address,
442 lease_length: lease_time.into(),
443 dns_server_count: dns_servers.as_ref().map(|list| list.len()).unwrap_or(0),
444 routers_count: routers.as_ref().map(|list| list.len()).unwrap_or(0),
445 };
446
447 Ok((
448 ClientWatchConfigurationResponse {
449 address: None,
450 dns_servers: dns_servers.map(into_fidl_list),
451 routers: routers.map(into_fidl_list),
452 ..Default::default()
453 },
454 LeaseChangeInspect::LeaseRenewed {
455 renewed_time: start_time,
456 properties: lease_inspect_properties,
457 },
458 ))
459 }
460
461 async fn remove_address_for_lease(&mut self, lease: Lease) -> Result<(), Error> {
462 let Lease { address_state_provider, assignment_state_stream, ip_address } = lease;
463 address_state_provider.remove().map_err(Error::Fidl)?;
464 let watch_result = assignment_state_stream
466 .filter_map(|result| futures::future::ready(result.err()))
467 .next()
468 .await;
469 let debug_log_prefix = &self.config.debug_log_prefix;
470
471 match watch_result {
472 None => log::error!(
473 "{debug_log_prefix} assignment_state_stream unexpectedly ended \
474 while watching for AddressRemovalReason after explicitly \
475 removing address {ip_address}",
476 ),
477 Some(fnet_interfaces_ext::admin::AddressStateProviderError::ChannelClosed) => {
478 log::error!(
479 "{debug_log_prefix} channel closed while watching for \
480 AddressRemovalReason after explicitly removing address {ip_address}",
481 )
482 }
483 Some(fnet_interfaces_ext::admin::AddressStateProviderError::Fidl(e)) => log::error!(
484 "{debug_log_prefix} error watching for \
485 AddressRemovalReason after explicitly removing address {ip_address}: {e:?}",
486 ),
487 Some(fnet_interfaces_ext::admin::AddressStateProviderError::AddressRemoved(reason)) => {
488 match reason {
489 fnet_interfaces_admin::AddressRemovalReason::UserRemoved => (),
490 reason @ (fnet_interfaces_admin::AddressRemovalReason::Invalid
491 | fnet_interfaces_admin::AddressRemovalReason::InvalidProperties
492 | fnet_interfaces_admin::AddressRemovalReason::AlreadyAssigned
493 | fnet_interfaces_admin::AddressRemovalReason::DadFailed
494 | fnet_interfaces_admin::AddressRemovalReason::Forfeited
495 | fnet_interfaces_admin::AddressRemovalReason::InterfaceRemoved) => {
496 log::error!(
497 "{debug_log_prefix} unexpected removal reason \
498 after explicitly removing address {ip_address}: {reason:?}",
499 );
500 }
501 }
502 }
503 };
504 Ok(())
505 }
506
507 async fn watch_configuration(
508 &mut self,
509 packet_socket_provider: &crate::packetsocket::PacketSocketProviderImpl,
510 udp_socket_provider: &impl dhcp_client_core::deps::UdpSocketProvider,
511 counters: &Counters,
512 inspect: &Inspect,
513 ) -> Result<ClientWatchConfigurationResponse, Error> {
514 loop {
515 let step = self
516 .watch_configuration_step(packet_socket_provider, udp_socket_provider, counters)
517 .await?;
518 let HandledWatchConfigurationStep { state_inspect, lease_inspect, response_to_return } =
519 self.handle_watch_configuration_step(step, packet_socket_provider).await?;
520
521 inspect.update(state_inspect, lease_inspect, self.config.debug_log_prefix);
525 if let Some(response) = response_to_return {
526 return Ok(response);
527 }
528 }
529 }
530
531 async fn handle_watch_configuration_step(
532 &mut self,
533 step: dhcp_client_core::client::Step<fasync::MonotonicInstant, ClientExitReason>,
534 _packet_socket_provider: &crate::packetsocket::PacketSocketProviderImpl,
535 ) -> Result<HandledWatchConfigurationStep, Error> {
536 let Self { core, rng: _, config, stop_receiver: _, current_lease: _, interface_id: _ } =
537 self;
538 match step {
539 dhcp_client_core::client::Step::NextState(transition) => {
540 let (next_core, effect) = core.apply(config, transition);
541 *core = next_core;
542 match effect {
543 Some(dhcp_client_core::client::TransitionEffect::DropLease {
544 address_rejected,
545 }) => {
546 let current_lease =
547 self.current_lease.take().expect("should have current lease");
548 if !address_rejected {
551 self.remove_address_for_lease(current_lease).await?;
552 }
553 Ok(HandledWatchConfigurationStep {
554 state_inspect: StateInspect {
555 state: next_core,
556 time: fasync::MonotonicInstant::now(),
557 },
558 lease_inspect: LeaseChangeInspect::LeaseDropped,
559 response_to_return: None,
560 })
561 }
562 Some(dhcp_client_core::client::TransitionEffect::HandleNewLease(
563 newly_acquired_lease,
564 )) => {
565 let (response, lease_inspect) =
566 self.handle_newly_acquired_lease(newly_acquired_lease).await?;
567 let start_time = fasync::MonotonicInstant::now();
568 Ok(HandledWatchConfigurationStep {
569 state_inspect: StateInspect { state: next_core, time: start_time },
570 lease_inspect,
571 response_to_return: Some(response),
572 })
573 }
574 Some(dhcp_client_core::client::TransitionEffect::HandleRenewedLease(
575 lease_renewal,
576 )) => {
577 let (response, lease_inspect) =
578 self.handle_lease_renewal(lease_renewal).await?;
579 Ok(HandledWatchConfigurationStep {
580 state_inspect: StateInspect {
581 state: next_core,
582 time: fasync::MonotonicInstant::now(),
583 },
584 lease_inspect,
585 response_to_return: Some(response),
586 })
587 }
588 None => Ok(HandledWatchConfigurationStep {
589 state_inspect: StateInspect {
590 state: *core,
591 time: fasync::MonotonicInstant::now(),
592 },
593 lease_inspect: LeaseChangeInspect::NoChange,
594 response_to_return: None,
595 }),
596 }
597 }
598 dhcp_client_core::client::Step::Exit(reason) => match reason {
599 dhcp_client_core::client::ExitReason::GracefulShutdown => {
600 if let Some(current_lease) = self.current_lease.take() {
601 self.remove_address_for_lease(current_lease).await?;
603 }
604 return Err(Error::Exit(ClientExitReason::GracefulShutdown));
605 }
606 dhcp_client_core::client::ExitReason::AddressRemoved(reason) => {
607 return Err(Error::Exit(reason));
608 }
609 },
610 }
611 }
612
613 async fn watch_configuration_step(
614 &mut self,
615 packet_socket_provider: &crate::packetsocket::PacketSocketProviderImpl,
616 udp_socket_provider: &impl dhcp_client_core::deps::UdpSocketProvider,
617 counters: &Counters,
618 ) -> Result<dhcp_client_core::client::Step<fasync::MonotonicInstant, ClientExitReason>, Error>
619 {
620 let Self { core, rng, config, stop_receiver, current_lease, interface_id } = self;
621 let clock = Clock;
622
623 let mut address_event_stream = match current_lease {
624 None => futures::stream::pending().left_stream(),
625 Some(Lease { address_state_provider: _, assignment_state_stream, ip_address }) => {
626 assignment_state_stream
627 .map(|event| into_address_event(event, config, ip_address, interface_id))
628 .right_stream()
629 }
630 }
631 .fuse();
632
633 core.run(
634 config,
635 packet_socket_provider,
636 udp_socket_provider,
637 rng,
638 &clock,
639 stop_receiver,
640 &mut address_event_stream,
641 counters,
642 )
643 .await
644 .map_err(Error::from_core)
645 }
646}
647
648fn into_address_event(
651 event: Result<
652 fnet_interfaces::AddressAssignmentState,
653 fnet_interfaces_ext::admin::AddressStateProviderError,
654 >,
655 config: &dhcp_client_core::client::ClientConfig,
656 ip_address: &SpecifiedAddr<Ipv4Addr>,
657 interface_id: &NonZeroU64,
658) -> dhcp_client_core::client::AddressEvent<ClientExitReason> {
659 let debug_log_prefix = &config.debug_log_prefix;
660 match event {
661 Ok(state) => {
662 let new_state = match state {
663 fnet_interfaces::AddressAssignmentState::Assigned => {
664 dhcp_client_core::client::AddressAssignmentState::Assigned
665 }
666 fnet_interfaces::AddressAssignmentState::Tentative => {
667 dhcp_client_core::client::AddressAssignmentState::Tentative
668 }
669 fnet_interfaces::AddressAssignmentState::Unavailable => {
670 dhcp_client_core::client::AddressAssignmentState::Unavailable
671 }
672 };
673 dhcp_client_core::client::AddressEvent::AssignmentStateChanged(new_state)
674 }
675 Err(fnet_interfaces_ext::admin::AddressStateProviderError::AddressRemoved(reason)) => {
676 match reason {
677 r @ fnet_interfaces_admin::AddressRemovalReason::Invalid
678 | r @ fnet_interfaces_admin::AddressRemovalReason::InvalidProperties => {
679 panic!("invalid address removal: {r:?}")
680 }
681 fnet_interfaces_admin::AddressRemovalReason::InterfaceRemoved => {
682 log::warn!("{debug_log_prefix} interface removed");
683 dhcp_client_core::client::AddressEvent::Removed(
684 ClientExitReason::InvalidInterface,
685 )
686 }
687 fnet_interfaces_admin::AddressRemovalReason::UserRemoved => {
688 log::warn!("{debug_log_prefix} address administratively removed");
689 dhcp_client_core::client::AddressEvent::Removed(
690 ClientExitReason::AddressRemovedByUser,
691 )
692 }
693 r @ fnet_interfaces_admin::AddressRemovalReason::AlreadyAssigned
694 | r @ fnet_interfaces_admin::AddressRemovalReason::DadFailed
695 | r @ fnet_interfaces_admin::AddressRemovalReason::Forfeited => {
696 log::warn!("{debug_log_prefix} address rejected: {r:?}");
697 dhcp_client_core::client::AddressEvent::Rejected
698 }
699 }
700 }
701 Err(fnet_interfaces_ext::admin::AddressStateProviderError::Fidl(e)) => {
702 log::error!(
703 "{debug_log_prefix} observed error {e:?} while watching for \
704 address event for address {ip_address} on interface {interface_id}; \
705 removing address",
706 );
707 dhcp_client_core::client::AddressEvent::Removed(
710 ClientExitReason::AddressStateProviderError,
711 )
712 }
713 Err(fnet_interfaces_ext::admin::AddressStateProviderError::ChannelClosed) => {
714 log::error!(
715 "{debug_log_prefix} observed channel closed while watching for \
716 address event for address {ip_address} on interface {interface_id};\
717 removing address",
718 );
719 dhcp_client_core::client::AddressEvent::Removed(
722 ClientExitReason::AddressStateProviderError,
723 )
724 }
725 }
726}
727
728struct HandledWatchConfigurationStep {
729 state_inspect: StateInspect,
730 lease_inspect: LeaseChangeInspect,
731 response_to_return: Option<ClientWatchConfigurationResponse>,
732}
733
734fn into_fidl_list(
735 list: impl IntoIterator<Item = std::net::Ipv4Addr>,
736) -> Vec<fidl_fuchsia_net::Ipv4Address> {
737 list.into_iter().map(|addr| net_types::ip::Ipv4Addr::from(addr).into_ext()).collect()
738}