dhcp_client/
client.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::cell::RefCell;
6use std::num::NonZeroU64;
7use std::sync::Arc;
8
9use anyhow::Context as _;
10use dhcp_client_core::inspect::Counters;
11use diagnostics_traits::Inspector;
12use fidl::endpoints::{self, Proxy as _};
13use fidl_fuchsia_net_dhcp::{
14    self as fdhcp, ClientExitReason, ClientRequestStream, ClientWatchConfigurationResponse,
15    ConfigurationToRequest, NewClientParams,
16};
17use fidl_fuchsia_net_ext::IntoExt as _;
18use futures::channel::mpsc;
19use futures::{select, FutureExt as _, TryStreamExt as _};
20use net_types::ip::{Ipv4, Ipv4Addr, PrefixLength};
21use net_types::{SpecifiedAddr, Witness as _};
22use rand::SeedableRng as _;
23use std::pin::pin;
24use {
25    fidl_fuchsia_net as fnet, fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin,
26    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        }
64    }
65}
66
67pub(crate) async fn serve_client(
68    mac: net_types::ethernet::Mac,
69    interface_id: NonZeroU64,
70    provider: &crate::packetsocket::PacketSocketProviderImpl,
71    udp_socket_provider: &impl dhcp_client_core::deps::UdpSocketProvider,
72    params: NewClientParams,
73    requests: ClientRequestStream,
74    inspect_root: &fuchsia_inspect::Node,
75) -> Result<(), Error> {
76    let (stop_sender, stop_receiver) = mpsc::unbounded();
77    let stop_sender = &stop_sender;
78    let debug_log_prefix = dhcp_client_core::client::DebugLogPrefix { interface_id };
79    let inspect = Arc::new(Inspect::new());
80    let client = RefCell::new(Client::new(
81        mac,
82        interface_id,
83        params,
84        rand::rngs::StdRng::seed_from_u64(rand::random()),
85        stop_receiver,
86        debug_log_prefix,
87    )?);
88    let counters = Arc::new(Counters::default());
89    let _node = inspect_root.create_lazy_child(interface_id.get().to_string(), {
90        let counters = counters.clone();
91        let inspect = inspect.clone();
92        move || {
93            let inspector = fuchsia_inspect::Inspector::default();
94            {
95                let mut inspector =
96                    diagnostics_traits::FuchsiaInspector::<'_, ()>::new(inspector.root());
97                inspector.record_uint("InterfaceId", interface_id.get());
98                inspect.record(&mut inspector);
99                inspector.record_child("Counters", |inspector| {
100                    counters.record(inspector);
101                });
102            }
103            Box::pin(futures::future::ready(Ok(inspector)))
104        }
105    });
106
107    let counters = counters.as_ref();
108    let inspect = inspect.as_ref();
109    requests
110        .map_err(Error::Fidl)
111        .try_for_each_concurrent(None, |request| {
112            let client = &client;
113            async move {
114                match request {
115                    fidl_fuchsia_net_dhcp::ClientRequest::WatchConfiguration { responder } => {
116                        let mut client = client.try_borrow_mut().map_err(|_| {
117                            Error::Exit(ClientExitReason::WatchConfigurationAlreadyPending)
118                        })?;
119                        responder
120                            .send(
121                                client
122                                    .watch_configuration(
123                                        provider,
124                                        udp_socket_provider,
125                                        counters,
126                                        inspect,
127                                    )
128                                    .await?,
129                            )
130                            .map_err(Error::Fidl)?;
131                        Ok(())
132                    }
133                    fidl_fuchsia_net_dhcp::ClientRequest::Shutdown { control_handle: _ } => {
134                        match stop_sender.unbounded_send(()) {
135                            Ok(()) => stop_sender.close_channel(),
136                            Err(try_send_error) => {
137                                // Note that `try_send_error` cannot be exhaustively matched on.
138                                if try_send_error.is_disconnected() {
139                                    log::warn!(
140                                        "{debug_log_prefix} tried to send shutdown request on \
141                                        already-closed channel to client core"
142                                    );
143                                } else {
144                                    log::error!(
145                                        "{debug_log_prefix} error while sending shutdown request \
146                                        to client core: {:?}",
147                                        try_send_error
148                                    );
149                                }
150                            }
151                        }
152                        Ok(())
153                    }
154                }
155            }
156        })
157        .await
158}
159
160struct Clock;
161
162impl dhcp_client_core::deps::Clock for Clock {
163    type Instant = fasync::MonotonicInstant;
164
165    fn now(&self) -> Self::Instant {
166        fasync::MonotonicInstant::now()
167    }
168
169    async fn wait_until(&self, time: Self::Instant) {
170        fasync::Timer::new(time).await
171    }
172}
173
174/// Encapsulates all DHCP client state.
175struct Client {
176    config: dhcp_client_core::client::ClientConfig,
177    core: dhcp_client_core::client::State<fasync::MonotonicInstant>,
178    rng: rand::rngs::StdRng,
179    stop_receiver: mpsc::UnboundedReceiver<()>,
180    current_lease: Option<Lease>,
181    interface_id: NonZeroU64,
182}
183
184struct Lease {
185    address_state_provider: fnet_interfaces_admin::AddressStateProviderProxy,
186    event_stream: fnet_interfaces_admin::AddressStateProviderEventStream,
187    ip_address: SpecifiedAddr<net_types::ip::Ipv4Addr>,
188}
189
190impl Lease {
191    async fn watch_for_address_removal(
192        &mut self,
193    ) -> Result<fnet_interfaces_admin::AddressRemovalReason, anyhow::Error> {
194        let Self { address_state_provider, event_stream, ip_address: _ } = self;
195
196        let _: zx::Signals = address_state_provider
197            .on_closed()
198            .await
199            .context("unexpected zx::Status while awaiting AddressStateProvider channel close")?;
200
201        let event_stream = event_stream.try_filter_map(|event| async move {
202            match event {
203                fnet_interfaces_admin::AddressStateProviderEvent::OnAddressAdded {} => Ok(None),
204                fnet_interfaces_admin::AddressStateProviderEvent::OnAddressRemoved { error } => {
205                    Ok(Some(error))
206                }
207            }
208        });
209        let mut event_stream = pin!(event_stream);
210
211        event_stream
212            .try_next()
213            .await
214            .context("AddressStateProviderEventStream FIDL error")?
215            .context("AddressStateProvider event stream ended without yielding removal reason")
216    }
217}
218
219impl Client {
220    fn new(
221        mac: net_types::ethernet::Mac,
222        interface_id: NonZeroU64,
223        NewClientParams { configuration_to_request, request_ip_address, .. }: NewClientParams,
224        rng: rand::rngs::StdRng,
225        stop_receiver: mpsc::UnboundedReceiver<()>,
226        debug_log_prefix: dhcp_client_core::client::DebugLogPrefix,
227    ) -> Result<Self, Error> {
228        if !request_ip_address.unwrap_or(false) {
229            log::error!(
230                "{debug_log_prefix} client creation failed: \
231                DHCPINFORM is unimplemented"
232            );
233            return Err(Error::Exit(ClientExitReason::InvalidParams));
234        }
235        let ConfigurationToRequest { routers, dns_servers, .. } =
236            configuration_to_request.unwrap_or_else(ConfigurationToRequest::default);
237
238        let config = dhcp_client_core::client::ClientConfig {
239            client_hardware_address: mac,
240            client_identifier: None,
241            requested_parameters: std::iter::once((
242                dhcp_protocol::OptionCode::SubnetMask,
243                dhcp_client_core::parse::OptionRequested::Required,
244            ))
245            .chain(routers.unwrap_or(false).then_some((
246                dhcp_protocol::OptionCode::Router,
247                dhcp_client_core::parse::OptionRequested::Optional,
248            )))
249            .chain(dns_servers.unwrap_or(false).then_some((
250                dhcp_protocol::OptionCode::DomainNameServer,
251                dhcp_client_core::parse::OptionRequested::Optional,
252            )))
253            .collect::<dhcp_client_core::parse::OptionCodeMap<_>>(),
254            preferred_lease_time_secs: None,
255            requested_ip_address: None,
256            debug_log_prefix,
257        };
258        Ok(Self {
259            core: dhcp_client_core::client::State::default(),
260            rng,
261            config,
262            stop_receiver,
263            current_lease: None,
264            interface_id,
265        })
266    }
267
268    async fn handle_newly_acquired_lease(
269        &mut self,
270        dhcp_client_core::client::NewlyAcquiredLease {
271            ip_address,
272            start_time,
273            lease_time,
274            parameters,
275        }: dhcp_client_core::client::NewlyAcquiredLease<fasync::MonotonicInstant>,
276    ) -> Result<(ClientWatchConfigurationResponse, LeaseChangeInspect), Error> {
277        let Self {
278            core: _,
279            rng: _,
280            config: dhcp_client_core::client::ClientConfig { debug_log_prefix, .. },
281            stop_receiver: _,
282            current_lease,
283            interface_id: _,
284        } = self;
285
286        let mut dns_servers: Option<Vec<_>> = None;
287        let mut routers: Option<Vec<_>> = None;
288        let mut prefix_len: Option<PrefixLength<Ipv4>> = None;
289        let mut unrequested_options = Vec::new();
290
291        for option in parameters {
292            match option {
293                dhcp_protocol::DhcpOption::SubnetMask(len) => {
294                    let previous_prefix_len = prefix_len.replace(len);
295                    if let Some(prev) = previous_prefix_len {
296                        log::warn!("expected previous_prefix_len to be None, got {prev:?}");
297                    }
298                }
299                dhcp_protocol::DhcpOption::DomainNameServer(list) => {
300                    let previous_dns_servers = dns_servers.replace(list.into());
301                    if let Some(prev) = previous_dns_servers {
302                        log::warn!("expected previous_dns_servers to be None, got {prev:?}");
303                    }
304                }
305                dhcp_protocol::DhcpOption::Router(list) => {
306                    let previous_routers = routers.replace(list.into());
307                    if let Some(prev) = previous_routers {
308                        log::warn!("expected previous_routers to be None, got {prev:?}");
309                    }
310                }
311                _ => {
312                    unrequested_options.push(option);
313                }
314            }
315        }
316
317        if !unrequested_options.is_empty() {
318            log::warn!(
319                "{debug_log_prefix} Received options from core that we didn't ask for: {:#?}",
320                unrequested_options
321            );
322        }
323
324        let prefix_len = prefix_len
325            .expect(
326                "subnet mask should be present \
327                because it was specified to core as required",
328            )
329            .get();
330
331        let (asp_proxy, asp_server_end) =
332            endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
333
334        let previous_lease = current_lease.replace(Lease {
335            event_stream: asp_proxy.take_event_stream(),
336            address_state_provider: asp_proxy,
337            ip_address,
338        });
339
340        if let Some(previous_lease) = previous_lease {
341            self.handle_lease_drop(previous_lease).await?;
342        }
343
344        let lease_inspect_properties = LeaseInspectProperties {
345            ip_address,
346            lease_length: lease_time.into(),
347            dns_server_count: dns_servers.as_ref().map(|list| list.len()).unwrap_or(0),
348            routers_count: routers.as_ref().map(|list| list.len()).unwrap_or(0),
349        };
350
351        Ok((
352            ClientWatchConfigurationResponse {
353                address: Some(fdhcp::Address {
354                    address: Some(fnet::Ipv4AddressWithPrefix {
355                        addr: ip_address.get().into_ext(),
356                        prefix_len,
357                    }),
358                    address_parameters: Some(fnet_interfaces_admin::AddressParameters {
359                        initial_properties: Some(fnet_interfaces_admin::AddressProperties {
360                            preferred_lifetime_info: None,
361                            valid_lifetime_end: Some(
362                                zx::MonotonicInstant::from(start_time + lease_time.into())
363                                    .into_nanos(),
364                            ),
365                            ..Default::default()
366                        }),
367                        add_subnet_route: Some(true),
368                        ..Default::default()
369                    }),
370                    address_state_provider: Some(asp_server_end),
371                    ..Default::default()
372                }),
373                dns_servers: dns_servers.map(into_fidl_list),
374                routers: routers.map(into_fidl_list),
375                ..Default::default()
376            },
377            LeaseChangeInspect::LeaseAdded {
378                start_time,
379                prefix_len,
380                properties: lease_inspect_properties,
381            },
382        ))
383    }
384
385    async fn handle_lease_renewal(
386        &mut self,
387        dhcp_client_core::client::LeaseRenewal {
388            start_time,
389            lease_time,
390            parameters,
391        }: dhcp_client_core::client::LeaseRenewal<fasync::MonotonicInstant>,
392    ) -> Result<(ClientWatchConfigurationResponse, LeaseChangeInspect), Error> {
393        let Self {
394            core: _,
395            rng: _,
396            config: dhcp_client_core::client::ClientConfig { debug_log_prefix, .. },
397            stop_receiver: _,
398            current_lease,
399            interface_id: _,
400        } = self;
401
402        let mut dns_servers: Option<Vec<_>> = None;
403        let mut routers: Option<Vec<_>> = None;
404        let mut unrequested_options = Vec::new();
405
406        for option in parameters {
407            match option {
408                dhcp_protocol::DhcpOption::SubnetMask(len) => {
409                    log::info!(
410                        "{debug_log_prefix} ignoring prefix length={:?} for renewed lease",
411                        len
412                    );
413                }
414                dhcp_protocol::DhcpOption::DomainNameServer(list) => {
415                    let prev = dns_servers.replace(list.into());
416                    if let Some(prev) = prev {
417                        log::warn!("expected prev_dns_servers to be None, got {prev:?}");
418                    }
419                }
420                dhcp_protocol::DhcpOption::Router(list) => {
421                    let prev = routers.replace(list.into());
422                    if let Some(prev) = prev {
423                        log::warn!("expected prev_routers to be None, got {prev:?}");
424                    }
425                }
426                option => {
427                    unrequested_options.push(option);
428                }
429            }
430        }
431
432        if !unrequested_options.is_empty() {
433            log::warn!(
434                "{debug_log_prefix} Received options from core that we didn't ask for: {:#?}",
435                unrequested_options
436            );
437        }
438
439        let Lease { event_stream: _, address_state_provider, ip_address } =
440            current_lease.as_mut().expect("should have current lease if we're handling a renewal");
441
442        address_state_provider
443            .update_address_properties(&fnet_interfaces_admin::AddressProperties {
444                preferred_lifetime_info: None,
445                valid_lifetime_end: Some(
446                    zx::MonotonicInstant::from(start_time + lease_time.into()).into_nanos(),
447                ),
448                ..Default::default()
449            })
450            .await
451            .map_err(Error::Fidl)?;
452
453        let lease_inspect_properties = LeaseInspectProperties {
454            ip_address: *ip_address,
455            lease_length: lease_time.into(),
456            dns_server_count: dns_servers.as_ref().map(|list| list.len()).unwrap_or(0),
457            routers_count: routers.as_ref().map(|list| list.len()).unwrap_or(0),
458        };
459
460        Ok((
461            ClientWatchConfigurationResponse {
462                address: None,
463                dns_servers: dns_servers.map(into_fidl_list),
464                routers: routers.map(into_fidl_list),
465                ..Default::default()
466            },
467            LeaseChangeInspect::LeaseRenewed {
468                renewed_time: start_time,
469                properties: lease_inspect_properties,
470            },
471        ))
472    }
473
474    async fn handle_lease_drop(&mut self, mut lease: Lease) -> Result<(), Error> {
475        lease.address_state_provider.remove().map_err(Error::Fidl)?;
476        let watch_result = lease.watch_for_address_removal().await;
477        let Lease { address_state_provider: _, event_stream: _, ip_address } = lease;
478        let debug_log_prefix = &self.config.debug_log_prefix;
479
480        match watch_result {
481            Err(e) => {
482                log::error!(
483                    "{debug_log_prefix} error watching for \
484                     AddressRemovalReason after explicitly removing address \
485                     {}: {:?}",
486                    ip_address,
487                    e
488                );
489            }
490            Ok(reason) => match reason {
491                fnet_interfaces_admin::AddressRemovalReason::UserRemoved => (),
492                reason @ (fnet_interfaces_admin::AddressRemovalReason::Invalid
493                | fnet_interfaces_admin::AddressRemovalReason::InvalidProperties
494                | fnet_interfaces_admin::AddressRemovalReason::AlreadyAssigned
495                | fnet_interfaces_admin::AddressRemovalReason::DadFailed
496                | fnet_interfaces_admin::AddressRemovalReason::InterfaceRemoved) => {
497                    log::error!(
498                        "{debug_log_prefix} unexpected removal reason \
499                        after explicitly removing address {}: {:?}",
500                        ip_address,
501                        reason
502                    );
503                }
504            },
505        };
506        Ok(())
507    }
508
509    async fn watch_configuration(
510        &mut self,
511        packet_socket_provider: &crate::packetsocket::PacketSocketProviderImpl,
512        udp_socket_provider: &impl dhcp_client_core::deps::UdpSocketProvider,
513        counters: &Counters,
514        inspect: &Inspect,
515    ) -> Result<ClientWatchConfigurationResponse, Error> {
516        loop {
517            let step = self
518                .watch_configuration_step(packet_socket_provider, udp_socket_provider, counters)
519                .await;
520            let HandledWatchConfigurationStep { state_inspect, lease_inspect, response_to_return } =
521                self.handle_watch_configuration_step(step, packet_socket_provider).await?;
522
523            // watch_configuration_step only resolves once there's been a state
524            // transition, so we should always update the state inspect and its
525            // history.
526            inspect.update(state_inspect, lease_inspect, self.config.debug_log_prefix);
527            if let Some(response) = response_to_return {
528                return Ok(response);
529            }
530        }
531    }
532
533    async fn handle_watch_configuration_step(
534        &mut self,
535        step: WatchConfigurationStep,
536        packet_socket_provider: &crate::packetsocket::PacketSocketProviderImpl,
537    ) -> Result<HandledWatchConfigurationStep, Error> {
538        let Self { core, rng: _, config, stop_receiver: _, current_lease, interface_id: _ } = self;
539        match step {
540            WatchConfigurationStep::CurrentLeaseAddressRemoved((reason, ip_address)) => {
541                *current_lease = None;
542                let debug_log_prefix = &config.debug_log_prefix;
543                match reason {
544                    None => return Err(Error::Exit(ClientExitReason::AddressStateProviderError)),
545                    Some(reason) => match reason {
546                        fnet_interfaces_admin::AddressRemovalReason::Invalid => {
547                            panic!("yielded invalid address")
548                        }
549                        fnet_interfaces_admin::AddressRemovalReason::InvalidProperties => {
550                            panic!("used invalid properties")
551                        }
552                        fnet_interfaces_admin::AddressRemovalReason::InterfaceRemoved => {
553                            log::warn!("{debug_log_prefix} interface removed; stopping");
554                            return Err(Error::Exit(ClientExitReason::InvalidInterface));
555                        }
556                        fnet_interfaces_admin::AddressRemovalReason::UserRemoved => {
557                            log::warn!(
558                                "{debug_log_prefix} address \
559                                administratively removed; stopping"
560                            );
561                            return Err(Error::Exit(ClientExitReason::AddressRemovedByUser));
562                        }
563                        fnet_interfaces_admin::AddressRemovalReason::AlreadyAssigned => {
564                            log::warn!(
565                                "{debug_log_prefix} address already assigned; notifying core"
566                            );
567                        }
568                        fnet_interfaces_admin::AddressRemovalReason::DadFailed => {
569                            log::warn!(
570                                "{debug_log_prefix} duplicate address detected; notifying core"
571                            );
572                        }
573                    },
574                };
575
576                match core
577                    .on_address_rejection(config, packet_socket_provider, &Clock, ip_address)
578                    .await
579                    .map_err(Error::from_core)?
580                {
581                    dhcp_client_core::client::AddressRejectionOutcome::ShouldBeImpossible => {
582                        unreachable!("should not observe address rejection without active lease");
583                    }
584                    dhcp_client_core::client::AddressRejectionOutcome::NextState(state) => {
585                        *core = state;
586                        Ok(HandledWatchConfigurationStep {
587                            state_inspect: StateInspect {
588                                state: *core,
589                                time: fasync::MonotonicInstant::now(),
590                            },
591                            lease_inspect: LeaseChangeInspect::LeaseDropped,
592                            response_to_return: None,
593                        })
594                    }
595                }
596            }
597            WatchConfigurationStep::CoreStep(core_step) => {
598                match core_step? {
599                    dhcp_client_core::client::Step::NextState(transition) => {
600                        let (next_core, effect) = core.apply(config, transition);
601                        *core = next_core;
602                        match effect {
603                            Some(dhcp_client_core::client::TransitionEffect::DropLease) => {
604                                let current_lease =
605                                    self.current_lease.take().expect("should have current lease");
606                                self.handle_lease_drop(current_lease).await?;
607                                Ok(HandledWatchConfigurationStep {
608                                    state_inspect: StateInspect {
609                                        state: next_core,
610                                        time: fasync::MonotonicInstant::now(),
611                                    },
612                                    lease_inspect: LeaseChangeInspect::LeaseDropped,
613                                    response_to_return: None,
614                                })
615                            }
616                            Some(dhcp_client_core::client::TransitionEffect::HandleNewLease(
617                                newly_acquired_lease,
618                            )) => {
619                                let (response, lease_inspect) =
620                                    self.handle_newly_acquired_lease(newly_acquired_lease).await?;
621                                let start_time = fasync::MonotonicInstant::now();
622                                Ok(HandledWatchConfigurationStep {
623                                    state_inspect: StateInspect {
624                                        state: next_core,
625                                        time: start_time,
626                                    },
627                                    lease_inspect,
628                                    response_to_return: Some(response),
629                                })
630                            }
631                            Some(
632                                dhcp_client_core::client::TransitionEffect::HandleRenewedLease(
633                                    lease_renewal,
634                                ),
635                            ) => {
636                                let (response, lease_inspect) =
637                                    self.handle_lease_renewal(lease_renewal).await?;
638                                Ok(HandledWatchConfigurationStep {
639                                    state_inspect: StateInspect {
640                                        state: next_core,
641                                        time: fasync::MonotonicInstant::now(),
642                                    },
643                                    lease_inspect,
644                                    response_to_return: Some(response),
645                                })
646                            }
647                            None => Ok(HandledWatchConfigurationStep {
648                                state_inspect: StateInspect {
649                                    state: *core,
650                                    time: fasync::MonotonicInstant::now(),
651                                },
652                                lease_inspect: LeaseChangeInspect::NoChange,
653                                response_to_return: None,
654                            }),
655                        }
656                    }
657                    dhcp_client_core::client::Step::Exit(reason) => match reason {
658                        dhcp_client_core::client::ExitReason::GracefulShutdown => {
659                            if let Some(current_lease) = self.current_lease.take() {
660                                // TODO(https://fxbug.dev/42079439): Send DHCPRELEASE.
661                                self.handle_lease_drop(current_lease).await?;
662                            }
663                            return Err(Error::Exit(ClientExitReason::GracefulShutdown));
664                        }
665                    },
666                }
667            }
668        }
669    }
670
671    async fn watch_configuration_step(
672        &mut self,
673        packet_socket_provider: &crate::packetsocket::PacketSocketProviderImpl,
674        udp_socket_provider: &impl dhcp_client_core::deps::UdpSocketProvider,
675        counters: &Counters,
676    ) -> WatchConfigurationStep {
677        let Self { core, rng, config, stop_receiver, current_lease, interface_id } = self;
678        let clock = Clock;
679
680        // Notice that we are `select`ing between the following two futures,
681        // and when one of them completes we throw away progress on the
682        // other one:
683        // 1) Watching for a previously-acquired address to be removed
684        // 2) Running the current state machine state
685        //
686        // If (1) completes, throwing away progress on (2) is okay because
687        // we always need to either transition back to the Init state or
688        // exit the client when an address is removed -- in both cases we're
689        // fine with throwing away whatever state-execution progress we'd
690        // previously made.
691        //
692        // If (2) completes, throwing away progress on (1) is okay because
693        // we'll be in one of two scenarios:
694        // a) We've transitioned to another state that maintains the same
695        //    lease (e.g. going from Bound to Renewing for the same
696        //    address). In this case, we won't modify the `current_lease`
697        //    field, and we'll observe that the AddressStateProvider has
698        //    been closed on the next iteration of this loop.
699        // b) We've transitioned to a state that entails removing the lease
700        //    (e.g. failing to Rebind and having to go back to Init), at
701        //    which point `current_lease` will be cleared for the next
702        //    iteration.
703        let mut core_step_fut = pin!(core
704            .run(
705                config,
706                packet_socket_provider,
707                udp_socket_provider,
708                rng,
709                &clock,
710                stop_receiver,
711                counters
712            )
713            .fuse());
714        let mut address_removed_fut = pin!(async {
715            match current_lease {
716                Some(current_lease) => match current_lease.watch_for_address_removal().await {
717                    Ok(reason) => (Some(reason), current_lease.ip_address),
718                    Err(e) => {
719                        let debug_log_prefix = &config.debug_log_prefix;
720                        log::error!(
721                            "{debug_log_prefix} observed error {:?} while watching for removal \
722                            of address {} on interface {}; \
723                            removing address",
724                            e,
725                            *current_lease.ip_address,
726                            interface_id
727                        );
728                        (None, current_lease.ip_address)
729                    }
730                },
731                None => futures::future::pending().await,
732            }
733        }
734        .fuse());
735
736        select! {
737            address_removed = address_removed_fut => {
738                WatchConfigurationStep::CurrentLeaseAddressRemoved(address_removed)
739            },
740            core_step = core_step_fut => {
741                WatchConfigurationStep::CoreStep(core_step.map_err(Error::from_core))
742            },
743        }
744    }
745}
746
747struct HandledWatchConfigurationStep {
748    state_inspect: StateInspect,
749    lease_inspect: LeaseChangeInspect,
750    response_to_return: Option<ClientWatchConfigurationResponse>,
751}
752
753enum WatchConfigurationStep {
754    CurrentLeaseAddressRemoved(
755        (Option<fnet_interfaces_admin::AddressRemovalReason>, SpecifiedAddr<Ipv4Addr>),
756    ),
757    CoreStep(Result<dhcp_client_core::client::Step<fasync::MonotonicInstant>, Error>),
758}
759
760fn into_fidl_list(list: Vec<std::net::Ipv4Addr>) -> Vec<fidl_fuchsia_net::Ipv4Address> {
761    list.into_iter().map(|addr| net_types::ip::Ipv4Addr::from(addr).into_ext()).collect()
762}