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, network, 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 netpol_networks_service: &mut network::NetpolNetworksService,
63 control: &fnet_interfaces_ext::admin::Control,
64 lookup_admin: &fnet_name::LookupAdminProxy,
65) {
66 let fnet_dhcp_ext::Configuration {
67 address,
68 dns_servers: new_dns_servers,
69 routers: new_routers,
70 ..
71 } = configuration;
72 if let Some(address) = address {
73 match address.add_to(control) {
74 Ok(()) => {}
75 Err((address, e)) => {
76 let fnet::Ipv4AddressWithPrefix { addr, prefix_len } = address;
77 warn!(
78 "error adding DHCPv4-acquired address {}/{} for interface {}: {}",
79 Ipv4Addr::from_ext(addr),
82 prefix_len,
83 interface_id,
84 e,
85 )
86 }
87 }
88 }
89
90 dns::update_servers(
91 lookup_admin,
92 dns_servers,
93 dns_server_watch_responders,
94 netpol_networks_service,
95 DnsServersUpdateSource::Dhcpv4 { interface_id: interface_id.get() },
96 new_dns_servers
97 .iter()
98 .map(|&address| fnet_name::DnsServer_ {
99 address: Some(fnet::SocketAddress::Ipv4(fnet::Ipv4SocketAddress {
100 address,
101 port: DEFAULT_DNS_PORT,
102 })),
103 source: Some(fnet_name::DnsServerSource::Dhcp(fnet_name::DhcpDnsServerSource {
104 source_interface: Some(interface_id.get()),
105 ..fnet_name::DhcpDnsServerSource::default()
106 })),
107 ..fnet_name::DnsServer_::default()
108 })
109 .collect(),
110 )
111 .await;
112
113 fnet_dhcp_ext::apply_new_routers(
114 interface_id.into(),
115 route_set,
116 configured_routers,
117 new_routers,
118 )
119 .await
120 .unwrap_or_else(|e| {
121 warn!("error applying new DHCPv4-acquired router configuration: {:?}", e);
122 })
123}
124
125pub(super) async fn start_client(
126 interface_id: InterfaceId,
127 interface_name: &str,
128 client_provider: &fnet_dhcp::ClientProviderProxy,
129 route_set_provider: &fnet_routes_admin::RouteTableV4Proxy,
130 interface_admin_auth: &fnet_interfaces_admin::GrantForInterfaceAuthorization,
131 configuration_streams: &mut ConfigurationStreamMap,
132) -> Result<ClientState, errors::Error> {
133 info!("starting DHCPv4 client for {} (id={})", interface_name, interface_id);
134
135 let (route_set, server_end) =
136 fidl::endpoints::create_proxy::<fnet_routes_admin::RouteSetV4Marker>();
137
138 route_set_provider.new_route_set(server_end).expect("create new route set");
139
140 let fnet_interfaces_admin::GrantForInterfaceAuthorization { interface_id: id, token } =
141 interface_admin_auth;
142
143 route_set
144 .authenticate_for_interface(fnet_interfaces_admin::ProofOfInterfaceAuthorization {
145 interface_id: *id,
146 token: token
147 .duplicate_handle(zx::Rights::TRANSFER)
148 .expect("interface auth grant is guaranteed to have ZX_RIGHT_DUPLICATE"),
149 })
150 .await
151 .map_err(|err| {
152 errors::Error::NonFatal(anyhow::anyhow!(
153 "FIDL error while getting authorization: {}",
154 err
155 ))
156 })?
157 .map_err(|err| {
158 errors::Error::NonFatal(anyhow::anyhow!("error while getting authorization: {:?}", err))
159 })?;
160
161 let client = client_provider.new_client_end_ext(interface_id.into(), new_client_params());
162 let (shutdown_sender, shutdown_receiver) = oneshot::channel();
163
164 if let Some(stream) = configuration_streams.insert(
165 interface_id,
166 fnet_dhcp_ext::merged_configuration_stream(
167 client,
168 shutdown_receiver.map(|receive_result| match receive_result {
169 Ok(()) => (),
170 Err(oneshot::Canceled) => (),
171 }),
172 )
173 .boxed()
174 .tagged(interface_id),
175 ) {
176 let _: Pin<Box<InterfaceIdTaggedConfigurationStream>> = stream;
177 unreachable!("only one DHCPv4 client may exist on {} (id={})", interface_name, interface_id)
178 }
179
180 Ok(ClientState { shutdown_sender, route_set, routers: Default::default() })
181}
182
183#[derive(Debug)]
184pub(super) enum AlreadyObservedClientExit {
185 Yes,
186 No,
187}
188
189const TIMEOUT_WAITING_FOR_CLIENT_SHUTDOWN: std::time::Duration = std::time::Duration::from_secs(10);
190
191pub(super) async fn stop_client(
192 interface_id: InterfaceId,
193 interface_name: &str,
194 mut state: ClientState,
195 configuration_streams: &mut ConfigurationStreamMap,
196 dns_servers: &mut DnsServers,
197 dns_server_watch_responders: &mut dns::DnsServerWatchResponders,
198 netpol_networks_service: &mut network::NetpolNetworksService,
199 control: &fnet_interfaces_ext::admin::Control,
200 lookup_admin: &fnet_name::LookupAdminProxy,
201 already_observed_exit: AlreadyObservedClientExit,
202) {
203 info!("stopping DHCPv4 client for {} (id={})", interface_name, interface_id);
204
205 update_configuration(
206 interface_id,
207 &mut state,
208 fnet_dhcp_ext::Configuration::default(),
209 dns_servers,
210 dns_server_watch_responders,
211 netpol_networks_service,
212 control,
213 lookup_admin,
214 )
215 .await;
216
217 let ClientState { shutdown_sender, route_set: _, routers: _ } = state;
218
219 let stream: Pin<Box<InterfaceIdTaggedConfigurationStream>> =
220 configuration_streams.remove(&interface_id).unwrap_or_else(|| {
221 unreachable!(
222 "DHCPv4 client must exist when stopping on {} (id={})",
223 interface_name, interface_id,
224 )
225 });
226
227 match already_observed_exit {
228 AlreadyObservedClientExit::Yes => {
229 drop(stream);
233 }
234 AlreadyObservedClientExit::No => {
235 shutdown_sender.send(()).expect(
237 "shutdown_receiver should not have been dropped, \
238 as we still hold a stream holding it",
239 );
240
241 let terminal_event_result = stream
243 .filter_map(|(_interface_id, item)| match item {
244 Ok(config) => {
245 warn!(
246 "observed DHCPv4 config stream while awaiting shutdown on \
247 {interface_name} (id={interface_id}): \
248 {config:?}"
249 );
250 futures::future::ready(None)
251 }
252 Err(err) => futures::future::ready(Some(err)),
253 })
254 .next()
255 .map(Some)
256 .on_timeout(TIMEOUT_WAITING_FOR_CLIENT_SHUTDOWN, || None)
259 .await;
260
261 match terminal_event_result {
262 None => {
263 log::error!(
264 "did not observe terminal event for DHCPv4 client on \
265 {interface_name} (id={interface_id})"
266 );
267 }
268 Some(err) => match err {
269 None => {
270 info!(
271 "DHCPv4 client gracefully shut down on {interface_name} \
272 (id={interface_id})"
273 );
274 }
275 Some(err) => {
276 warn!(
277 "DHCPv4 client exited on {interface_name} (id={interface_id}) \
278 (error={err:?})"
279 );
280 }
281 },
282 }
283 }
284 }
285}
286
287pub(super) async fn start_server(
289 dhcp_server: &fnet_dhcp::Server_Proxy,
290) -> Result<(), errors::Error> {
291 dhcp_server
292 .start_serving()
293 .await
294 .context("error sending start DHCP server request")
295 .map_err(errors::Error::NonFatal)?
296 .map_err(zx::Status::from_raw)
297 .context("error starting DHCP server")
298 .map_err(errors::Error::NonFatal)
299}
300
301pub(super) async fn stop_server(
303 dhcp_server: &fnet_dhcp::Server_Proxy,
304) -> Result<(), errors::Error> {
305 dhcp_server
306 .stop_serving()
307 .await
308 .context("error sending stop DHCP server request")
309 .map_err(errors::Error::NonFatal)
310}