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