dhcp_client/
provider.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.
4use std::cell::RefCell;
5use std::collections::HashSet;
6use std::num::NonZeroU64;
7
8use fidl_fuchsia_net_dhcp::{ClientExitReason, ClientProviderRequest, ClientProviderRequestStream};
9use fidl_fuchsia_posix_socket_packet as fpacket;
10use futures::{StreamExt as _, TryStreamExt as _};
11
12use crate::udpsocket::LibcUdpSocketProvider;
13
14#[derive(thiserror::Error, Debug)]
15pub(crate) enum Error {
16    #[error("the interface identifier was zero")]
17    InvalidInterfaceIdentifier,
18    #[error("tried to create multiple DHCP clients with interface_id={0}; exiting")]
19    MultipleClientsOnSameInterface(NonZeroU64),
20}
21
22struct InterfacesInUse {
23    set: RefCell<HashSet<NonZeroU64>>,
24}
25
26impl InterfacesInUse {
27    fn new() -> Self {
28        Self { set: RefCell::new(HashSet::new()) }
29    }
30
31    fn mark_interface_in_use(
32        &self,
33        interface_id: NonZeroU64,
34    ) -> Result<InterfaceInUseHandle<'_>, AlreadyInUse> {
35        let Self { set } = self;
36        if set.borrow_mut().insert(interface_id) {
37            Ok(InterfaceInUseHandle { parent: self, interface_id })
38        } else {
39            Err(AlreadyInUse)
40        }
41    }
42
43    fn remove(&self, interface_id: NonZeroU64) {
44        let Self { set } = self;
45        assert!(set.borrow_mut().remove(&interface_id));
46    }
47}
48
49#[must_use]
50struct InterfaceInUseHandle<'a> {
51    parent: &'a InterfacesInUse,
52    interface_id: NonZeroU64,
53}
54
55impl<'a> Drop for InterfaceInUseHandle<'a> {
56    fn drop(&mut self) {
57        let Self { parent, interface_id } = self;
58        parent.remove(*interface_id);
59    }
60}
61
62struct AlreadyInUse;
63
64pub(crate) async fn serve_client_provider(
65    stream: ClientProviderRequestStream,
66    provider: fpacket::ProviderProxy,
67    inspect_root: &fuchsia_inspect::Node,
68) -> Result<(), Error> {
69    let provider = &provider;
70    let interfaces_in_use = &InterfacesInUse::new();
71
72    stream
73        .filter_map(|result| {
74            futures::future::ready(result.map(Some).unwrap_or_else(|error| match error {
75                fidl::Error::ClientChannelClosed { .. } => None,
76                error => {
77                    panic!("unexpected FIDL error in client provider request stream: {error:?}")
78                }
79            }))
80        })
81        .map(Ok)
82        .try_for_each_concurrent(None, |request| async move {
83            match request {
84                ClientProviderRequest::NewClient {
85                    interface_id,
86                    params,
87                    request,
88                    control_handle: _,
89                } => {
90                    let interface_id =
91                        NonZeroU64::new(interface_id).ok_or(Error::InvalidInterfaceIdentifier)?;
92                    let udp_socket_provider = LibcUdpSocketProvider { interface_id };
93
94                    let (client_requests_stream, control_handle) =
95                        request.into_stream_and_control_handle();
96                    let _handle: InterfaceInUseHandle<'_> = {
97                        match interfaces_in_use.mark_interface_in_use(interface_id) {
98                            Ok(handle) => handle,
99                            Err(AlreadyInUse) => {
100                                control_handle
101                                    .send_on_exit(ClientExitReason::ClientAlreadyExistsOnInterface)
102                                    .unwrap_or_else(|e| {
103                                        log::error!(
104                                            "FIDL error while sending on_exit event: {:?}",
105                                            e
106                                        );
107                                    });
108                                return Err(Error::MultipleClientsOnSameInterface(interface_id));
109                            }
110                        }
111                    };
112
113                    let provider = &crate::packetsocket::PacketSocketProviderImpl::new(
114                        provider.clone(),
115                        interface_id,
116                    );
117
118                    let mac = match provider.get_mac().await {
119                        Ok(mac) => mac,
120                        Err(e) => {
121                            log::warn!("error while getting MAC address: {:?}", e);
122                            control_handle
123                                .send_on_exit({
124                                    match e {
125                                        dhcp_client_core::deps::SocketError::UnsupportedHardwareType
126                                        | dhcp_client_core::deps::SocketError::NoInterface => {
127                                            ClientExitReason::InvalidInterface
128                                        }
129                                        dhcp_client_core::deps::SocketError::FailedToOpen(_)
130                                        | dhcp_client_core::deps::SocketError::HostUnreachable
131                                        | dhcp_client_core::deps::SocketError::NetworkUnreachable
132                                        | dhcp_client_core::deps::SocketError::Other(_) => {
133                                            ClientExitReason::UnableToOpenSocket
134                                        }
135                                    }
136                                })
137                                .unwrap_or_else(|e| {
138                                    log::error!("FIDL error while sending on_exit event: {:?}", e);
139                                });
140                            return Ok(());
141                        }
142                    };
143
144                    crate::client::serve_client(
145                        mac,
146                        interface_id,
147                        provider,
148                        &udp_socket_provider,
149                        params,
150                        client_requests_stream,
151                        inspect_root,
152                    )
153                    .await
154                    .unwrap_or_else(|error| match error {
155                        crate::client::Error::Exit(reason) => {
156                            log::info!(
157                                "(interface_id = {interface_id}) client exiting: {:?}",
158                                reason
159                            );
160                            control_handle.send_on_exit(reason).unwrap_or_else(|e| {
161                                log::error!("FIDL error while sending on_exit event: {:?}", e);
162                            });
163                        }
164                        crate::client::Error::Fidl(e) => {
165                            if e.is_closed() {
166                                log::warn!("Channel closed while serving client: {:?}", e);
167                            } else {
168                                log::error!("FIDL error while serving client: {:?}", e);
169                            }
170                        }
171                        crate::client::Error::Core(e) => {
172                            log::warn!("error while serving client: {:?}", e);
173                        }
174                    });
175                    Ok(())
176                }
177                ClientProviderRequest::CheckPresence { responder } => {
178                    // This is a no-op method, so ignore any errors.
179                    let _: Result<(), fidl::Error> = responder.send();
180                    Ok(())
181                }
182            }
183        })
184        .await
185}