1use std::collections::HashSet;
6use std::pin::Pin;
7
8use fidl_fuchsia_net_dhcp_ext::{self as fnet_dhcp_ext, ClientProviderExt as _};
9use fidl_fuchsia_net_ext::FromExt as _;
10use {
11 fidl_fuchsia_net as fnet, fidl_fuchsia_net_dhcp as fnet_dhcp,
12 fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin,
13 fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext, fidl_fuchsia_net_name as fnet_name,
14 fidl_fuchsia_net_routes_admin as fnet_routes_admin,
15};
16
17use anyhow::Context as _;
18use async_utils::stream::{StreamMap, Tagged, WithTag as _};
19use dns_server_watcher::{DnsServers, DnsServersUpdateSource, DEFAULT_DNS_PORT};
20use fuchsia_async::TimeoutExt as _;
21use futures::channel::oneshot;
22use futures::stream::StreamExt as _;
23use futures::FutureExt;
24use log::{info, warn};
25use net_types::ip::Ipv4Addr;
26use net_types::SpecifiedAddr;
27use zx::HandleBased;
28
29use crate::{dns, errors, InterfaceId};
30
31#[derive(Debug)]
32pub(super) struct ClientState {
33 routers: HashSet<SpecifiedAddr<Ipv4Addr>>,
34 route_set: fnet_routes_admin::RouteSetV4Proxy,
35 shutdown_sender: oneshot::Sender<()>,
36}
37
38pub(super) fn new_client_params() -> fnet_dhcp::NewClientParams {
39 fnet_dhcp_ext::default_new_client_params()
40}
41
42pub(super) type ConfigurationStreamMap =
43 StreamMap<InterfaceId, InterfaceIdTaggedConfigurationStream>;
44pub(super) type InterfaceIdTaggedConfigurationStream = Tagged<InterfaceId, ConfigurationStream>;
45pub(super) type ConfigurationStream =
46 futures::stream::BoxStream<'static, Result<fnet_dhcp_ext::Configuration, fnet_dhcp_ext::Error>>;
47
48pub(super) async fn probe_for_presence(provider: &fnet_dhcp::ClientProviderProxy) -> bool {
49 match provider.check_presence().await {
50 Ok(()) => true,
51 Err(fidl::Error::ClientChannelClosed { .. }) => false,
52 Err(e) => panic!("unexpected error while probing: {e}"),
53 }
54}
55
56pub(super) async fn update_configuration(
57 interface_id: InterfaceId,
58 ClientState { shutdown_sender: _, routers: configured_routers, route_set }: &mut ClientState,
59 configuration: fnet_dhcp_ext::Configuration,
60 dns_servers: &mut DnsServers,
61 dns_server_watch_responders: &mut dns::DnsServerWatchResponders,
62 control: &fnet_interfaces_ext::admin::Control,
63 lookup_admin: &fnet_name::LookupAdminProxy,
64) {
65 let fnet_dhcp_ext::Configuration {
66 address,
67 dns_servers: new_dns_servers,
68 routers: new_routers,
69 ..
70 } = configuration;
71 if let Some(address) = address {
72 match address.add_to(control) {
73 Ok(()) => {}
74 Err((address, e)) => {
75 let fnet::Ipv4AddressWithPrefix { addr, prefix_len } = address;
76 warn!(
77 "error adding DHCPv4-acquired address {}/{} for interface {}: {}",
78 Ipv4Addr::from_ext(addr),
81 prefix_len,
82 interface_id,
83 e,
84 )
85 }
86 }
87 }
88
89 dns::update_servers(
90 lookup_admin,
91 dns_servers,
92 dns_server_watch_responders,
93 DnsServersUpdateSource::Dhcpv4 { interface_id: interface_id.get() },
94 new_dns_servers
95 .iter()
96 .map(|&address| fnet_name::DnsServer_ {
97 address: Some(fnet::SocketAddress::Ipv4(fnet::Ipv4SocketAddress {
98 address,
99 port: DEFAULT_DNS_PORT,
100 })),
101 source: Some(fnet_name::DnsServerSource::Dhcp(fnet_name::DhcpDnsServerSource {
102 source_interface: Some(interface_id.get()),
103 ..fnet_name::DhcpDnsServerSource::default()
104 })),
105 ..fnet_name::DnsServer_::default()
106 })
107 .collect(),
108 )
109 .await;
110
111 fnet_dhcp_ext::apply_new_routers(
112 interface_id.into(),
113 route_set,
114 configured_routers,
115 new_routers,
116 )
117 .await
118 .unwrap_or_else(|e| {
119 warn!("error applying new DHCPv4-acquired router configuration: {:?}", e);
120 })
121}
122
123pub(super) async fn start_client(
124 interface_id: InterfaceId,
125 interface_name: &str,
126 client_provider: &fnet_dhcp::ClientProviderProxy,
127 route_set_provider: &fnet_routes_admin::RouteTableV4Proxy,
128 interface_admin_auth: &fnet_interfaces_admin::GrantForInterfaceAuthorization,
129 configuration_streams: &mut ConfigurationStreamMap,
130) -> Result<ClientState, errors::Error> {
131 info!("starting DHCPv4 client for {} (id={})", interface_name, interface_id);
132
133 let (route_set, server_end) =
134 fidl::endpoints::create_proxy::<fnet_routes_admin::RouteSetV4Marker>();
135
136 route_set_provider.new_route_set(server_end).expect("create new route set");
137
138 let fnet_interfaces_admin::GrantForInterfaceAuthorization { interface_id: id, token } =
139 interface_admin_auth;
140
141 route_set
142 .authenticate_for_interface(fnet_interfaces_admin::ProofOfInterfaceAuthorization {
143 interface_id: *id,
144 token: token
145 .duplicate_handle(zx::Rights::TRANSFER)
146 .expect("interface auth grant is guaranteed to have ZX_RIGHT_DUPLICATE"),
147 })
148 .await
149 .map_err(|err| {
150 errors::Error::NonFatal(anyhow::anyhow!(
151 "FIDL error while getting authorization: {}",
152 err
153 ))
154 })?
155 .map_err(|err| {
156 errors::Error::NonFatal(anyhow::anyhow!("error while getting authorization: {:?}", err))
157 })?;
158
159 let client = client_provider.new_client_end_ext(interface_id.into(), new_client_params());
160 let (shutdown_sender, shutdown_receiver) = oneshot::channel();
161
162 if let Some(stream) = configuration_streams.insert(
163 interface_id,
164 fnet_dhcp_ext::merged_configuration_stream(
165 client,
166 shutdown_receiver.map(|receive_result| match receive_result {
167 Ok(()) => (),
168 Err(oneshot::Canceled) => (),
169 }),
170 )
171 .boxed()
172 .tagged(interface_id),
173 ) {
174 let _: Pin<Box<InterfaceIdTaggedConfigurationStream>> = stream;
175 unreachable!("only one DHCPv4 client may exist on {} (id={})", interface_name, interface_id)
176 }
177
178 Ok(ClientState { shutdown_sender, route_set, routers: Default::default() })
179}
180
181#[derive(Debug)]
182pub(super) enum AlreadyObservedClientExit {
183 Yes,
184 No,
185}
186
187const TIMEOUT_WAITING_FOR_CLIENT_SHUTDOWN: std::time::Duration = std::time::Duration::from_secs(10);
188
189pub(super) async fn stop_client(
190 interface_id: InterfaceId,
191 interface_name: &str,
192 mut state: ClientState,
193 configuration_streams: &mut ConfigurationStreamMap,
194 dns_servers: &mut DnsServers,
195 dns_server_watch_responders: &mut dns::DnsServerWatchResponders,
196 control: &fnet_interfaces_ext::admin::Control,
197 lookup_admin: &fnet_name::LookupAdminProxy,
198 already_observed_exit: AlreadyObservedClientExit,
199) {
200 info!("stopping DHCPv4 client for {} (id={})", interface_name, interface_id);
201
202 update_configuration(
203 interface_id,
204 &mut state,
205 fnet_dhcp_ext::Configuration::default(),
206 dns_servers,
207 dns_server_watch_responders,
208 control,
209 lookup_admin,
210 )
211 .await;
212
213 let ClientState { shutdown_sender, route_set: _, routers: _ } = state;
214
215 let stream: Pin<Box<InterfaceIdTaggedConfigurationStream>> =
216 configuration_streams.remove(&interface_id).unwrap_or_else(|| {
217 unreachable!(
218 "DHCPv4 client must exist when stopping on {} (id={})",
219 interface_name, interface_id,
220 )
221 });
222
223 match already_observed_exit {
224 AlreadyObservedClientExit::Yes => {
225 drop(stream);
229 }
230 AlreadyObservedClientExit::No => {
231 shutdown_sender.send(()).expect(
233 "shutdown_receiver should not have been dropped, \
234 as we still hold a stream holding it",
235 );
236
237 let terminal_event_result = stream
239 .filter_map(|(_interface_id, item)| match item {
240 Ok(config) => {
241 warn!(
242 "observed DHCPv4 config stream while awaiting shutdown on \
243 {interface_name} (id={interface_id}): \
244 {config:?}"
245 );
246 futures::future::ready(None)
247 }
248 Err(err) => futures::future::ready(Some(err)),
249 })
250 .next()
251 .map(Some)
252 .on_timeout(TIMEOUT_WAITING_FOR_CLIENT_SHUTDOWN, || None)
255 .await;
256
257 match terminal_event_result {
258 None => {
259 log::error!(
260 "did not observe terminal event for DHCPv4 client on \
261 {interface_name} (id={interface_id})"
262 );
263 }
264 Some(err) => match err {
265 None => {
266 info!(
267 "DHCPv4 client gracefully shut down on {interface_name} \
268 (id={interface_id})"
269 );
270 }
271 Some(err) => {
272 warn!(
273 "DHCPv4 client exited on {interface_name} (id={interface_id}) \
274 (error={err:?})"
275 );
276 }
277 },
278 }
279 }
280 }
281}
282
283pub(super) async fn start_server(
285 dhcp_server: &fnet_dhcp::Server_Proxy,
286) -> Result<(), errors::Error> {
287 dhcp_server
288 .start_serving()
289 .await
290 .context("error sending start DHCP server request")
291 .map_err(errors::Error::NonFatal)?
292 .map_err(zx::Status::from_raw)
293 .context("error starting DHCP server")
294 .map_err(errors::Error::NonFatal)
295}
296
297pub(super) async fn stop_server(
299 dhcp_server: &fnet_dhcp::Server_Proxy,
300) -> Result<(), errors::Error> {
301 dhcp_server
302 .stop_serving()
303 .await
304 .context("error sending stop DHCP server request")
305 .map_err(errors::Error::NonFatal)
306}