1use std::collections::{HashMap, HashSet};
6
7use {
8 fidl_fuchsia_net as fnet, fidl_fuchsia_net_dhcpv6 as fnet_dhcpv6,
9 fidl_fuchsia_net_dhcpv6_ext as fnet_dhcpv6_ext, fidl_fuchsia_net_ext as fnet_ext,
10 fidl_fuchsia_net_name as fnet_name,
11};
12
13use anyhow::Context as _;
14use async_utils::hanging_get::client::HangingGetStream;
15use async_utils::stream::{StreamMap, Tagged};
16use dns_server_watcher::{DnsServers, DnsServersUpdateSource};
17use futures::future::TryFutureExt as _;
18use futures::stream::{Stream, TryStreamExt as _};
19use log::warn;
20
21use crate::{dns, errors, network, DnsServerWatchers, InterfaceId};
22
23pub(super) fn duid(mac: fnet_ext::MacAddress) -> fnet_dhcpv6::Duid {
25 fnet_dhcpv6::Duid::LinkLayerAddress(fnet_dhcpv6::LinkLayerAddress::Ethernet(mac.into()))
26}
27
28#[derive(Copy, Clone, Debug, PartialEq)]
29pub(super) struct PrefixOnInterface {
30 interface_id: InterfaceId,
31 prefix: net_types::ip::Subnet<net_types::ip::Ipv6Addr>,
32 lifetimes: Lifetimes,
33}
34
35pub(super) type Prefixes = HashMap<net_types::ip::Subnet<net_types::ip::Ipv6Addr>, Lifetimes>;
36pub(super) type InterfaceIdTaggedPrefixesStream = Tagged<InterfaceId, PrefixesStream>;
37pub(super) type PrefixesStreamMap = StreamMap<InterfaceId, InterfaceIdTaggedPrefixesStream>;
38
39#[derive(Debug)]
40pub(super) struct ClientState {
41 pub(super) sockaddr: fnet::Ipv6SocketAddress,
42 pub(super) prefixes: Prefixes,
43}
44
45impl ClientState {
46 pub(super) fn new(sockaddr: fnet::Ipv6SocketAddress) -> Self {
47 Self { sockaddr, prefixes: Default::default() }
48 }
49}
50
51#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
52pub(super) struct Lifetimes {
53 preferred_until: zx::MonotonicInstant,
54 valid_until: zx::MonotonicInstant,
55}
56
57impl Into<fnet_dhcpv6::Lifetimes> for Lifetimes {
58 fn into(self) -> fnet_dhcpv6::Lifetimes {
59 let Self { preferred_until, valid_until } = self;
60 fnet_dhcpv6::Lifetimes {
61 preferred_until: preferred_until.into_nanos(),
62 valid_until: valid_until.into_nanos(),
63 }
64 }
65}
66
67pub(super) type PrefixesStream =
68 HangingGetStream<fnet_dhcpv6::ClientProxy, Vec<fnet_dhcpv6::Prefix>>;
69
70pub(super) fn from_fidl_prefixes(
71 fidl_prefixes: &[fnet_dhcpv6::Prefix],
72) -> Result<Prefixes, anyhow::Error> {
73 let prefixes = fidl_prefixes
74 .iter()
75 .map(
76 |&fnet_dhcpv6::Prefix {
77 prefix:
78 fnet::Ipv6AddressWithPrefix { addr: fnet::Ipv6Address { addr }, prefix_len },
79 lifetimes: fnet_dhcpv6::Lifetimes { valid_until, preferred_until },
80 }| {
81 let subnet = net_types::ip::Subnet::new(
82 net_types::ip::Ipv6Addr::from_bytes(addr),
83 prefix_len,
84 )
85 .map_err(|e| anyhow::anyhow!("subnet parsing error: {:?}", e))?;
86 if valid_until == 0 {
87 return Err(anyhow::anyhow!(
88 "received DHCPv6 prefix {:?} with valid-until time of 0",
89 subnet
90 ));
91 }
92 if preferred_until == 0 {
93 return Err(anyhow::anyhow!(
94 "received DHCPv6 prefix {:?} with preferred-until time of 0",
95 subnet
96 ));
97 }
98 Ok((
99 subnet,
100 Lifetimes {
101 valid_until: zx::MonotonicInstant::from_nanos(valid_until),
102 preferred_until: zx::MonotonicInstant::from_nanos(preferred_until),
103 },
104 ))
105 },
106 )
107 .collect::<Result<Prefixes, _>>()?;
108 if prefixes.len() != fidl_prefixes.len() {
109 return Err(anyhow::anyhow!(
110 "DHCPv6 prefixes {:?} contains duplicate prefix",
111 fidl_prefixes
112 ));
113 }
114 Ok(prefixes)
115}
116
117pub(super) fn start_client(
119 dhcpv6_client_provider: &fnet_dhcpv6::ClientProviderProxy,
120 interface_id: InterfaceId,
121 sockaddr: fnet::Ipv6SocketAddress,
122 duid: fnet_dhcpv6::Duid,
123 prefix_delegation_config: Option<fnet_dhcpv6::PrefixDelegationConfig>,
124) -> Result<
125 (impl Stream<Item = Result<Vec<fnet_name::DnsServer_>, fidl::Error>>, PrefixesStream),
126 errors::Error,
127> {
128 let stateful = prefix_delegation_config.is_some();
129 let params = fnet_dhcpv6_ext::NewClientParams {
130 interface_id: interface_id.get(),
131 address: sockaddr,
132 config: fnet_dhcpv6_ext::ClientConfig {
133 information_config: fnet_dhcpv6_ext::InformationConfig { dns_servers: true },
134 non_temporary_address_config: Default::default(),
135 prefix_delegation_config,
136 },
137 duid: stateful.then_some(duid),
138 };
139 let (client, server) = fidl::endpoints::create_proxy::<fnet_dhcpv6::ClientMarker>();
140
141 dhcpv6_client_provider
144 .new_client(¶ms.into(), server)
145 .context("error creating new DHCPv6 client")
146 .map_err(errors::Error::NonFatal)?;
147
148 let dns_servers_stream = futures::stream::try_unfold(client.clone(), move |proxy| {
149 proxy.watch_servers().map_ok(move |s| Some((s, proxy)))
150 });
151 let prefixes_stream =
152 PrefixesStream::new_eager_with_fn_ptr(client, fnet_dhcpv6::ClientProxy::watch_prefixes);
153
154 Ok((dns_servers_stream, prefixes_stream))
155}
156
157fn get_suitable_dhcpv6_prefix(
158 current_prefix: Option<PrefixOnInterface>,
159 interface_states: &HashMap<InterfaceId, crate::InterfaceState>,
160 allowed_upstream_device_classes: &HashSet<crate::DeviceClass>,
161 interface_config: AcquirePrefixInterfaceConfig,
162) -> Option<PrefixOnInterface> {
163 if let Some(PrefixOnInterface { interface_id, prefix, lifetimes: _ }) = current_prefix {
164 let crate::InterfaceState { config, .. } =
165 interface_states.get(&interface_id).unwrap_or_else(|| {
166 panic!(
167 "interface {} cannot be found but provides current prefix = {:?}",
168 interface_id, current_prefix,
169 )
170 });
171 match config {
172 crate::InterfaceConfigState::Host(crate::HostInterfaceState {
173 dhcpv4_client: _,
174 dhcpv6_client_state,
175 dhcpv6_pd_config: _,
176 interface_admin_auth: _,
177 interface_naming_id: _,
178 }) => {
179 let Some(ClientState { prefixes, sockaddr: _ }) = dhcpv6_client_state.as_ref()
180 else {
181 return None;
184 };
185 if let Some(lifetimes) = prefixes.get(&prefix) {
186 return Some(PrefixOnInterface { interface_id, prefix, lifetimes: *lifetimes });
187 }
188 }
189 crate::InterfaceConfigState::WlanAp(wlan_ap_state) => {
190 panic!(
191 "interface {} not expected to be WLAN AP with state: {:?}",
192 interface_id, wlan_ap_state,
193 );
194 }
195 crate::InterfaceConfigState::Blackhole(state) => {
196 panic!(
197 "interface {} not expected to be blackhole with state: {:?}",
198 interface_id, state,
199 );
200 }
201 }
202 }
203
204 interface_states
205 .iter()
206 .filter_map(|(interface_id, crate::InterfaceState { config, device_class, .. })| {
207 let prefixes = match config {
208 crate::InterfaceConfigState::Host(crate::HostInterfaceState {
209 dhcpv4_client: _,
210 dhcpv6_client_state,
211 dhcpv6_pd_config: _,
212 interface_admin_auth: _,
213 interface_naming_id: _,
214 }) => {
215 if let Some(ClientState { prefixes, sockaddr: _ }) = dhcpv6_client_state {
216 prefixes
217 } else {
218 return None;
219 }
220 }
221 crate::InterfaceConfigState::WlanAp(crate::WlanApInterfaceState {
222 interface_naming_id: _,
223 })
224 | crate::InterfaceConfigState::Blackhole(_) => {
225 return None;
226 }
227 };
228 match interface_config {
229 AcquirePrefixInterfaceConfig::Upstreams => {
230 allowed_upstream_device_classes.contains(&device_class)
231 }
232 AcquirePrefixInterfaceConfig::Id(want_id) => interface_id.get() == want_id,
233 }
234 .then(|| {
235 prefixes.iter().map(|(&prefix, &lifetimes)| PrefixOnInterface {
236 interface_id: *interface_id,
237 prefix,
238 lifetimes,
239 })
240 })
241 })
242 .flatten()
243 .max_by(
244 |PrefixOnInterface {
245 interface_id: _,
246 prefix: _,
247 lifetimes:
248 Lifetimes { preferred_until: preferred_until1, valid_until: valid_until1 },
249 },
250 PrefixOnInterface {
251 interface_id: _,
252 prefix: _,
253 lifetimes:
254 Lifetimes { preferred_until: preferred_until2, valid_until: valid_until2 },
255 }| {
256 (preferred_until1, valid_until1).cmp(&(preferred_until2, valid_until2))
259 },
260 )
261}
262
263pub(super) fn maybe_send_watch_prefix_response(
264 interface_states: &HashMap<InterfaceId, crate::InterfaceState>,
265 allowed_upstream_device_classes: &HashSet<crate::DeviceClass>,
266 prefix_provider_handler: Option<&mut PrefixProviderHandler>,
267) -> Result<(), anyhow::Error> {
268 let PrefixProviderHandler {
269 current_prefix,
270 interface_config,
271 preferred_prefix_len: _,
272 watch_prefix_responder,
273 prefix_control_request_stream: _,
274 } = if let Some(handler) = prefix_provider_handler {
275 handler
276 } else {
277 return Ok(());
278 };
279
280 let new_prefix = get_suitable_dhcpv6_prefix(
281 *current_prefix,
282 interface_states,
283 allowed_upstream_device_classes,
284 *interface_config,
285 );
286 if new_prefix == *current_prefix {
287 return Ok(());
288 }
289
290 if let Some(responder) = watch_prefix_responder.take() {
291 responder
292 .send(&new_prefix.map_or(
293 fnet_dhcpv6::PrefixEvent::Unassigned(fnet_dhcpv6::Empty),
294 |PrefixOnInterface { interface_id: _, prefix, lifetimes }| {
295 fnet_dhcpv6::PrefixEvent::Assigned(fnet_dhcpv6::Prefix {
296 prefix: fnet::Ipv6AddressWithPrefix {
297 addr: fnet::Ipv6Address { addr: prefix.network().ipv6_bytes() },
298 prefix_len: prefix.prefix(),
299 },
300 lifetimes: lifetimes.into(),
301 })
302 },
303 ))
304 .context("failed to send PrefixControl.WatchPrefix response")?;
305 *current_prefix = new_prefix;
306 }
307
308 Ok(())
309}
310
311pub(super) async fn stop_client(
315 lookup_admin: &fnet_name::LookupAdminProxy,
316 dns_servers: &mut DnsServers,
317 dns_server_watch_responders: &mut dns::DnsServerWatchResponders,
318 netpol_networks_service: &mut network::NetpolNetworksService,
319 interface_id: InterfaceId,
320 watchers: &mut DnsServerWatchers<'_>,
321 prefixes_streams: &mut PrefixesStreamMap,
322) {
323 let source = DnsServersUpdateSource::Dhcpv6 { interface_id: interface_id.get() };
324
325 if let None = watchers.remove(&source) {
327 warn!(
331 "DNS Watcher for key not present; multiple futures stopped DHCPv6 \
332 client for key {:?}; interface_id={}",
333 source, interface_id
334 );
335 }
336 if let None = prefixes_streams.remove(&interface_id) {
337 warn!(
341 "Prefix Stream for key not present; multiple futures stopped DHCPv6 \
342 client for key {:?}; interface_id={}",
343 source, interface_id
344 );
345 }
346
347 dns::update_servers(
348 lookup_admin,
349 dns_servers,
350 dns_server_watch_responders,
351 netpol_networks_service,
352 source,
353 vec![],
354 )
355 .await
356}
357
358#[derive(Clone, Copy, PartialEq, Eq, Hash)]
359pub(super) enum AcquirePrefixInterfaceConfig {
360 Upstreams,
361 Id(u64),
362}
363
364pub(super) struct PrefixProviderHandler {
365 pub(super) prefix_control_request_stream: fnet_dhcpv6::PrefixControlRequestStream,
366 pub(super) watch_prefix_responder: Option<fnet_dhcpv6::PrefixControlWatchPrefixResponder>,
367 pub(super) preferred_prefix_len: Option<u8>,
368 pub(super) interface_config: AcquirePrefixInterfaceConfig,
370 pub(super) current_prefix: Option<PrefixOnInterface>,
371}
372
373impl PrefixProviderHandler {
374 pub(super) fn try_next_prefix_control_request(
375 &mut self,
376 ) -> futures::stream::TryNext<'_, fnet_dhcpv6::PrefixControlRequestStream> {
377 self.prefix_control_request_stream.try_next()
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
384
385 use net_declare::{fidl_socket_addr_v6, net_subnet_v6};
386 use test_case::test_case;
387
388 use crate::interface::{generate_identifier, InterfaceNamingIdentifier, ProvisioningAction};
389 use crate::{DeviceClass, HostInterfaceState, InterfaceConfigState, InterfaceState};
390
391 use super::*;
392
393 const ALLOWED_UPSTREAM_DEVICE_CLASS: crate::DeviceClass = crate::DeviceClass::Ethernet;
394 const DISALLOWED_UPSTREAM_DEVICE_CLASS: crate::DeviceClass = crate::DeviceClass::Virtual;
395 const LIFETIMES: Lifetimes = Lifetimes {
396 preferred_until: zx::MonotonicInstant::from_nanos(123_000_000_000),
397 valid_until: zx::MonotonicInstant::from_nanos(456_000_000_000),
398 };
399 const RENEWED_LIFETIMES: Lifetimes = Lifetimes {
400 preferred_until: zx::MonotonicInstant::from_nanos(777_000_000_000),
401 valid_until: zx::MonotonicInstant::from_nanos(888_000_000_000),
402 };
403
404 impl InterfaceState {
405 fn new_host_with_state(
406 interface_naming_id: InterfaceNamingIdentifier,
407 control: fidl_fuchsia_net_interfaces_ext::admin::Control,
408 device_class: DeviceClass,
409 dhcpv6_pd_config: Option<fnet_dhcpv6::PrefixDelegationConfig>,
410 dhcpv6_client_state: Option<ClientState>,
411 provisioning: ProvisioningAction,
412 interface_admin_auth: fnet_interfaces_admin::GrantForInterfaceAuthorization,
413 ) -> Self {
414 Self {
415 control,
416 config: InterfaceConfigState::Host(HostInterfaceState {
417 dhcpv4_client: crate::Dhcpv4ClientState::NotRunning,
418 dhcpv6_client_state,
419 dhcpv6_pd_config,
420 interface_admin_auth,
421 interface_naming_id,
422 }),
423 device_class,
424 provisioning,
425 }
426 }
427 }
428
429 fn fake_interface_grant() -> fnet_interfaces_admin::GrantForInterfaceAuthorization {
430 fnet_interfaces_admin::GrantForInterfaceAuthorization {
431 interface_id: 0,
432 token: zx::Event::create(),
433 }
434 }
435
436 #[test_case(
437 None,
438 [
439 (
440 DISALLOWED_UPSTREAM_DEVICE_CLASS,
441 Some(HashMap::from([(net_subnet_v6!("abcd::/64"), LIFETIMES)])),
442 )
443 ].into_iter(),
444 AcquirePrefixInterfaceConfig::Upstreams,
445 None;
446 "not_upstream"
447 )]
448 #[test_case(
449 None,
450 [
451 (
452 ALLOWED_UPSTREAM_DEVICE_CLASS,
453 Some(HashMap::from([(net_subnet_v6!("abcd::/64"), LIFETIMES)])),
454 )
455 ].into_iter(),
456 AcquirePrefixInterfaceConfig::Upstreams,
457 Some(PrefixOnInterface {
458 interface_id: InterfaceId::new(1).unwrap(),
459 prefix: net_subnet_v6!("abcd::/64"),
460 lifetimes: LIFETIMES,
461 });
462 "none_to_some"
463 )]
464 #[test_case(
465 Some(PrefixOnInterface {
466 interface_id: InterfaceId::new(1).unwrap(),
467 prefix: net_subnet_v6!("abcd::/64"),
468 lifetimes: LIFETIMES,
469 }),
470 [
471 (
472 ALLOWED_UPSTREAM_DEVICE_CLASS,
473 Some(HashMap::from([(net_subnet_v6!("abcd::/64"), LIFETIMES)])),
474 )
475 ].into_iter(),
476 AcquirePrefixInterfaceConfig::Upstreams,
477 Some(PrefixOnInterface {
478 interface_id: InterfaceId::new(1).unwrap(),
479 prefix: net_subnet_v6!("abcd::/64"),
480 lifetimes: LIFETIMES,
481 });
482 "same"
483 )]
484 #[test_case(
485 Some(PrefixOnInterface {
486 interface_id: InterfaceId::new(1).unwrap(),
487 prefix: net_subnet_v6!("abcd::/64"),
488 lifetimes: LIFETIMES,
489 }),
490 [
491 (
492 ALLOWED_UPSTREAM_DEVICE_CLASS,
493 Some(HashMap::from([(net_subnet_v6!("abcd::/64"), RENEWED_LIFETIMES)])),
494 )
495 ].into_iter(),
496 AcquirePrefixInterfaceConfig::Upstreams,
497 Some(PrefixOnInterface {
498 interface_id: InterfaceId::new(1).unwrap(),
499 prefix: net_subnet_v6!("abcd::/64"),
500 lifetimes: RENEWED_LIFETIMES,
501 });
502 "lifetime_changed"
503 )]
504 #[test_case(
505 Some(PrefixOnInterface {
506 interface_id: InterfaceId::new(1).unwrap(),
507 prefix: net_subnet_v6!("abcd::/64"),
508 lifetimes: LIFETIMES,
509 }),
510 [
511 (
512 ALLOWED_UPSTREAM_DEVICE_CLASS,
513 Some(HashMap::new()),
514 ),
515 (
516 ALLOWED_UPSTREAM_DEVICE_CLASS,
517 Some(HashMap::from([(net_subnet_v6!("efff::/64"), RENEWED_LIFETIMES)])),
518 )
519 ].into_iter(),
520 AcquirePrefixInterfaceConfig::Upstreams,
521 Some(PrefixOnInterface {
522 interface_id: InterfaceId::new(2).unwrap(),
523 prefix: net_subnet_v6!("efff::/64"),
524 lifetimes: RENEWED_LIFETIMES,
525 });
526 "different_interface"
527 )]
528 #[fuchsia::test]
529 async fn get_suitable_dhcpv6_prefix(
530 current_prefix: Option<PrefixOnInterface>,
531 interface_state_iter: impl IntoIterator<Item = (crate::DeviceClass, Option<Prefixes>)>,
532 interface_config: AcquirePrefixInterfaceConfig,
533 want: Option<PrefixOnInterface>,
534 ) {
535 let interface_states = (1..)
536 .flat_map(InterfaceId::new)
537 .zip(interface_state_iter.into_iter().map(|(device_class, prefixes)| {
538 let (control, _control_server_end) =
539 fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
540 .expect("create endpoints");
541 InterfaceState::new_host_with_state(
542 generate_identifier(&fidl_fuchsia_net_ext::MacAddress {
543 octets: [0x1, 0x2, 0x3, 0x4, 0x5, 0x6],
544 }),
545 control,
546 device_class,
547 None,
548 prefixes.map(|prefixes| ClientState {
549 sockaddr: fidl_socket_addr_v6!("[fe80::1]:546"),
550 prefixes: prefixes,
551 }),
552 ProvisioningAction::Local,
553 fake_interface_grant(),
554 )
555 }))
556 .collect();
557 let allowed_upstream_device_classes = HashSet::from([ALLOWED_UPSTREAM_DEVICE_CLASS]);
558 assert_eq!(
559 super::get_suitable_dhcpv6_prefix(
560 current_prefix,
561 &interface_states,
562 &allowed_upstream_device_classes,
563 interface_config,
564 ),
565 want
566 );
567 }
568}