1use 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 let _: Result<(), fidl::Error> = responder.send();
180 Ok(())
181 }
182 }
183 })
184 .await
185}