1use std::collections::hash_map::DefaultHasher;
7use std::collections::{HashMap, HashSet};
8use std::hash::{Hash, Hasher};
9use std::net::{IpAddr, SocketAddr};
10use std::ops::Add;
11use std::pin::Pin;
12use std::str::FromStr as _;
13use std::time::Duration;
14
15use fidl::endpoints::{ControlHandle as _, ServerEnd};
16use fidl_fuchsia_net as fnet;
17use fidl_fuchsia_net_dhcpv6::{
18 ClientMarker, ClientRequest, ClientRequestStream, ClientWatchAddressResponder,
19 ClientWatchPrefixesResponder, ClientWatchServersResponder, Duid, Empty, Lifetimes,
20 LinkLayerAddress, LinkLayerAddressPlusTime, Prefix, PrefixDelegationConfig,
21 RELAY_AGENT_AND_SERVER_LINK_LOCAL_MULTICAST_ADDRESS, RELAY_AGENT_AND_SERVER_PORT,
22};
23use fidl_fuchsia_net_dhcpv6_ext::{
24 AddressConfig, ClientConfig, InformationConfig, NewClientParams,
25};
26use fidl_fuchsia_net_ext as fnet_ext;
27use fidl_fuchsia_net_name as fnet_name;
28use fuchsia_async as fasync;
29use futures::{Future, FutureExt as _, StreamExt as _, TryStreamExt as _, select, stream};
30
31use anyhow::{Context as _, Result};
32use assert_matches::assert_matches;
33use byteorder::{NetworkEndian, WriteBytesExt as _};
34use dns_server_watcher::DEFAULT_DNS_PORT;
35use log::{debug, warn};
36use net_types::MulticastAddress as _;
37use net_types::ip::{Ip as _, Ipv6, Ipv6Addr, Subnet, SubnetError};
38use packet::ParsablePacket;
39use packet_formats_dhcp::v6;
40use rand::SeedableRng;
41use rand::rngs::StdRng;
42
43#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
45pub(crate) struct MonotonicInstant(zx::MonotonicInstant);
46
47impl MonotonicInstant {
48 fn now() -> MonotonicInstant {
49 MonotonicInstant(zx::MonotonicInstant::get())
50 }
51}
52
53impl dhcpv6_core::Instant for MonotonicInstant {
54 fn duration_since(&self, MonotonicInstant(earlier): MonotonicInstant) -> Duration {
55 let Self(this) = *self;
56
57 let diff: zx::MonotonicDuration = this - earlier;
58
59 Duration::from_nanos(diff.into_nanos().try_into().unwrap_or_else(|e| {
60 panic!(
61 "failed to calculate duration since {:?} with instant {:?}: {}",
62 earlier, this, e,
63 )
64 }))
65 }
66
67 fn checked_add(&self, duration: Duration) -> Option<MonotonicInstant> {
68 Some(self.add(duration))
69 }
70}
71
72impl Add<Duration> for MonotonicInstant {
73 type Output = MonotonicInstant;
74
75 fn add(self, duration: Duration) -> MonotonicInstant {
76 let MonotonicInstant(this) = self;
77 MonotonicInstant(this + duration.into())
78 }
79}
80
81#[derive(Debug, thiserror::Error)]
82pub enum ClientError {
83 #[error("fidl error")]
84 Fidl(#[source] fidl::Error),
85 #[error("got watch request while the previous one is pending")]
86 DoubleWatch,
87 #[error("unsupported DHCPv6 configuration")]
88 UnsupportedConfigs,
89 #[error("socket create error")]
90 SocketCreate(std::io::Error),
91 #[error("socket receive error")]
92 SocketRecv(std::io::Error),
93 #[error("unimplemented DHCPv6 functionality: {:?}()", _0)]
94 Unimplemented(String),
95}
96
97const MAX_UDP_DATAGRAM_SIZE: usize = 65_535;
101
102#[pin_project::pin_project]
103struct Timers {
104 #[pin]
105 retransmission: fasync::Timer,
106 #[pin]
107 refresh: fasync::Timer,
108 #[pin]
109 renew: fasync::Timer,
110 #[pin]
111 rebind: fasync::Timer,
112 #[pin]
113 restart_server_discovery: fasync::Timer,
114
115 #[cfg(test)]
116 scheduled: HashSet<dhcpv6_core::client::ClientTimerType>,
117}
118
119impl Default for Timers {
120 fn default() -> Self {
121 let unscheduled = || fasync::Timer::new(fasync::MonotonicInstant::INFINITE);
122 Self {
123 retransmission: unscheduled(),
124 refresh: unscheduled(),
125 renew: unscheduled(),
126 rebind: unscheduled(),
127 restart_server_discovery: unscheduled(),
128 #[cfg(test)]
129 scheduled: Default::default(),
130 }
131 }
132}
133
134pub(crate) struct Client<S: for<'a> AsyncSocket<'a>> {
136 interface_id: fnet::InterfaceId,
138 last_observed_dns_hash: u64,
143 dns_responder: Option<ClientWatchServersResponder>,
145 address_responder: Option<ClientWatchAddressResponder>,
147 prefixes: HashMap<fnet::Ipv6AddressWithPrefix, Lifetimes>,
149 prefixes_changed: bool,
151 prefixes_responder: Option<ClientWatchPrefixesResponder>,
153 state_machine: dhcpv6_core::client::ClientStateMachine<MonotonicInstant, StdRng>,
155 socket: S,
157 server_addr: SocketAddr,
159 timers: Pin<Box<Timers>>,
161 request_stream: ClientRequestStream,
163}
164
165pub(crate) trait AsyncSocket<'a> {
167 type RecvFromFut: Future<Output = Result<(usize, SocketAddr), std::io::Error>> + 'a;
168 type SendToFut: Future<Output = Result<usize, std::io::Error>> + 'a;
169
170 fn recv_from(&'a self, buf: &'a mut [u8]) -> Self::RecvFromFut;
171 fn send_to(&'a self, buf: &'a [u8], addr: SocketAddr) -> Self::SendToFut;
172}
173
174impl<'a> AsyncSocket<'a> for fasync::net::UdpSocket {
175 type RecvFromFut = fasync::net::UdpRecvFrom<'a>;
176 type SendToFut = fasync::net::SendTo<'a>;
177
178 fn recv_from(&'a self, buf: &'a mut [u8]) -> Self::RecvFromFut {
179 self.recv_from(buf)
180 }
181 fn send_to(&'a self, buf: &'a [u8], addr: SocketAddr) -> Self::SendToFut {
182 self.send_to(buf, addr)
183 }
184}
185
186fn to_dhcpv6_option_codes(
188 InformationConfig { dns_servers }: InformationConfig,
189) -> Vec<v6::OptionCode> {
190 dns_servers.then_some(v6::OptionCode::DnsServers).into_iter().collect()
191}
192
193fn to_configured_addresses(
194 AddressConfig { address_count, preferred_addresses }: AddressConfig,
195) -> Result<HashMap<v6::IAID, HashSet<Ipv6Addr>>, ClientError> {
196 let preferred_addresses = preferred_addresses.unwrap_or(Vec::new());
197 if preferred_addresses.len() > address_count.into() {
198 return Err(ClientError::UnsupportedConfigs);
199 }
200
201 Ok((0..)
204 .map(v6::IAID::new)
205 .zip(
206 preferred_addresses
207 .into_iter()
208 .map(|fnet::Ipv6Address { addr, .. }| HashSet::from([Ipv6Addr::from(addr)]))
209 .chain(std::iter::repeat_with(HashSet::new)),
210 )
211 .take(address_count.into())
212 .collect())
213}
214
215const IA_PD_IAID: v6::IAID = v6::IAID::new(0);
219
220fn create_state_machine(
222 duid: Option<dhcpv6_core::ClientDuid>,
223 transaction_id: [u8; 3],
224 ClientConfig {
225 information_config,
226 non_temporary_address_config,
227 prefix_delegation_config,
228 }: ClientConfig,
229) -> Result<
230 (
231 dhcpv6_core::client::ClientStateMachine<MonotonicInstant, StdRng>,
232 dhcpv6_core::client::Actions<MonotonicInstant>,
233 ),
234 ClientError,
235> {
236 let information_option_codes = to_dhcpv6_option_codes(information_config);
237 let configured_non_temporary_addresses = to_configured_addresses(non_temporary_address_config)?;
238 let configured_delegated_prefixes = prefix_delegation_config
239 .map(|prefix_delegation_config| {
240 let prefix = match prefix_delegation_config {
241 PrefixDelegationConfig::Empty(Empty {}) => Ok(None),
242 PrefixDelegationConfig::PrefixLength(prefix_len) => {
243 if prefix_len == 0 {
244 return Err(ClientError::UnsupportedConfigs);
246 }
247
248 Subnet::new(Ipv6::UNSPECIFIED_ADDRESS, prefix_len).map(Some)
249 }
250 PrefixDelegationConfig::Prefix(fnet::Ipv6AddressWithPrefix {
251 addr: fnet::Ipv6Address { addr, .. },
252 prefix_len,
253 }) => {
254 let addr = Ipv6Addr::from_bytes(addr);
255 if addr == Ipv6::UNSPECIFIED_ADDRESS {
256 return Err(ClientError::UnsupportedConfigs);
258 }
259
260 Subnet::new(addr, prefix_len).map(Some)
261 }
262 };
263
264 match prefix {
265 Ok(o) => Ok(HashMap::from([(IA_PD_IAID, HashSet::from_iter(o.into_iter()))])),
266 Err(SubnetError::PrefixTooLong | SubnetError::HostBitsSet) => {
267 Err(ClientError::UnsupportedConfigs)
268 }
269 }
270 })
271 .transpose()?;
272
273 let now = MonotonicInstant::now();
274 match (
275 information_option_codes.is_empty(),
276 configured_non_temporary_addresses.is_empty(),
277 configured_delegated_prefixes,
278 ) {
279 (true, true, None) => Err(ClientError::UnsupportedConfigs),
280 (false, true, None) => {
281 if duid.is_some() {
282 Err(ClientError::UnsupportedConfigs)
283 } else {
284 Ok(dhcpv6_core::client::ClientStateMachine::start_stateless(
285 transaction_id,
286 information_option_codes,
287 StdRng::from_os_rng(),
288 now,
289 ))
290 }
291 }
292 (
293 _request_information,
294 _configure_non_temporary_addresses,
295 configured_delegated_prefixes,
296 ) => Ok(dhcpv6_core::client::ClientStateMachine::start_stateful(
297 transaction_id,
298 if let Some(duid) = duid {
299 duid
300 } else {
301 return Err(ClientError::UnsupportedConfigs);
302 },
303 configured_non_temporary_addresses,
304 configured_delegated_prefixes.unwrap_or_else(Default::default),
305 information_option_codes,
306 StdRng::from_os_rng(),
307 now,
308 )),
309 }
310}
311
312fn hash<H: Hash>(h: &H) -> u64 {
314 let mut dh = DefaultHasher::new();
315 h.hash(&mut dh);
316 dh.finish()
317}
318
319fn subnet_to_address_with_prefix(prefix: Subnet<Ipv6Addr>) -> fnet::Ipv6AddressWithPrefix {
320 fnet::Ipv6AddressWithPrefix {
321 addr: fnet::Ipv6Address { addr: prefix.network().ipv6_bytes() },
322 prefix_len: prefix.prefix(),
323 }
324}
325
326impl<S: for<'a> AsyncSocket<'a>> Client<S> {
327 pub(crate) async fn start(
331 duid: Option<dhcpv6_core::ClientDuid>,
332 transaction_id: [u8; 3],
333 config: ClientConfig,
334 interface_id: fnet::InterfaceId,
335 socket_fn: impl FnOnce() -> std::io::Result<S>,
336 server_addr: SocketAddr,
337 request_stream: ClientRequestStream,
338 ) -> Result<Self, ClientError> {
339 let (state_machine, actions) = create_state_machine(duid, transaction_id, config)?;
340 let mut client = Self {
341 state_machine,
342 interface_id,
343 socket: socket_fn().map_err(ClientError::SocketCreate)?,
344 server_addr,
345 request_stream,
346 last_observed_dns_hash: hash(&Vec::<Ipv6Addr>::new()),
349 dns_responder: None,
350 address_responder: None,
351 prefixes: Default::default(),
352 prefixes_changed: false,
353 prefixes_responder: None,
354 timers: Box::pin(Default::default()),
355 };
356 client.run_actions(actions).await?;
357 Ok(client)
358 }
359
360 async fn run_actions(
362 &mut self,
363 actions: dhcpv6_core::client::Actions<MonotonicInstant>,
364 ) -> Result<(), ClientError> {
365 stream::iter(actions)
366 .map(Ok)
367 .try_fold(self, |client, action| async move {
368 match action {
369 dhcpv6_core::client::Action::SendMessage(buf) => {
370 match client.socket.send_to(&buf, client.server_addr).await {
371 Ok(size) => assert_eq!(size, buf.len()),
372 Err(e) => warn!(
373 "failed to send message to {}: {}; will retransmit later",
374 client.server_addr, e
375 ),
376 };
377 }
378 dhcpv6_core::client::Action::ScheduleTimer(timer_type, timeout) => {
379 client.schedule_timer(timer_type, timeout)
380 }
381 dhcpv6_core::client::Action::CancelTimer(timer_type) => {
382 client.cancel_timer(timer_type)
383 }
384 dhcpv6_core::client::Action::UpdateDnsServers(servers) => {
385 client.maybe_send_dns_server_updates(servers)?;
386 }
387 dhcpv6_core::client::Action::IaNaUpdates(_) => {
388 }
395 dhcpv6_core::client::Action::IaPdUpdates(mut updates) => {
396 let updates = {
397 let ret =
398 updates.remove(&IA_PD_IAID).expect("Update missing for IAID");
399 debug_assert_eq!(updates, HashMap::new());
400 ret
401 };
402
403 let Self { prefixes, prefixes_changed, .. } = client;
404
405 let now = zx::MonotonicInstant::get();
406 let nonzero_timevalue_to_zx_time = |tv| match tv {
407 v6::NonZeroTimeValue::Finite(tv) => {
408 now + zx::MonotonicDuration::from_seconds(tv.get().into())
409 }
410 v6::NonZeroTimeValue::Infinity => zx::MonotonicInstant::INFINITE,
411 };
412
413 let calculate_lifetimes = |dhcpv6_core::client::Lifetimes {
414 preferred_lifetime,
415 valid_lifetime,
416 }| {
417 Lifetimes {
418 preferred_until: match preferred_lifetime {
419 v6::TimeValue::Zero => zx::MonotonicInstant::ZERO,
420 v6::TimeValue::NonZero(preferred_lifetime) => {
421 nonzero_timevalue_to_zx_time(preferred_lifetime)
422 },
423 }.into_nanos(),
424 valid_until: nonzero_timevalue_to_zx_time(valid_lifetime)
425 .into_nanos(),
426 }
427 };
428
429 for (prefix, update) in updates.into_iter() {
430 let fidl_prefix = subnet_to_address_with_prefix(prefix);
431
432 match update {
433 dhcpv6_core::client::IaValueUpdateKind::Added(lifetimes) => {
434 assert_matches!(
435 prefixes.insert(
436 fidl_prefix,
437 calculate_lifetimes(lifetimes)
438 ),
439 None,
440 "must not know about prefix {} to add it with lifetimes {:?}",
441 prefix, lifetimes,
442 );
443 }
444 dhcpv6_core::client::IaValueUpdateKind::UpdatedLifetimes(updated_lifetimes) => {
445 assert_matches!(
446 prefixes.get_mut(&fidl_prefix),
447 Some(lifetimes) => {
448 *lifetimes = calculate_lifetimes(updated_lifetimes);
449 },
450 "must know about prefix {} to update lifetimes with {:?}",
451 prefix, updated_lifetimes,
452 );
453 }
454 dhcpv6_core::client::IaValueUpdateKind::Removed => {
455 assert_matches!(
456 prefixes.remove(&fidl_prefix),
457 Some(_),
458 "must know about prefix {} to remove it",
459 prefix
460 );
461 }
462 }
463 }
464
465 *prefixes_changed = true;
468 client.maybe_send_prefixes()?;
469 }
470 };
471 Ok(client)
472 })
473 .await
474 .map(|_: &mut Client<S>| ())
475 }
476
477 fn maybe_send_dns_server_updates(&mut self, servers: Vec<Ipv6Addr>) -> Result<(), ClientError> {
480 let servers_hash = hash(&servers);
481 if servers_hash == self.last_observed_dns_hash {
482 Ok(())
483 } else {
484 Ok(match self.dns_responder.take() {
485 Some(responder) => {
486 self.send_dns_server_updates(responder, servers, servers_hash)?
487 }
488 None => (),
489 })
490 }
491 }
492
493 fn maybe_send_prefixes(&mut self) -> Result<(), ClientError> {
494 let Self { prefixes, prefixes_changed, prefixes_responder, .. } = self;
495
496 if !*prefixes_changed {
497 return Ok(());
498 }
499
500 let responder = if let Some(responder) = prefixes_responder.take() {
501 responder
502 } else {
503 return Ok(());
504 };
505
506 let prefixes = prefixes
507 .iter()
508 .map(|(prefix, lifetimes)| Prefix { prefix: *prefix, lifetimes: *lifetimes })
509 .collect::<Vec<_>>();
510
511 responder.send(&prefixes).map_err(ClientError::Fidl)?;
512 *prefixes_changed = false;
513 Ok(())
514 }
515
516 fn send_dns_server_updates(
519 &mut self,
520 responder: ClientWatchServersResponder,
521 servers: Vec<Ipv6Addr>,
522 hash: u64,
523 ) -> Result<(), ClientError> {
524 let response: Vec<_> = servers
525 .iter()
526 .map(|addr| {
527 let address = fnet::Ipv6Address { addr: addr.ipv6_bytes() };
528 let zone_index = if addr.is_unicast_link_local() { self.interface_id } else { 0 };
529
530 fnet_name::DnsServer_ {
531 address: Some(fnet::SocketAddress::Ipv6(fnet::Ipv6SocketAddress {
532 address,
533 zone_index,
534 port: DEFAULT_DNS_PORT,
535 })),
536 source: Some(fnet_name::DnsServerSource::Dhcpv6(
537 fnet_name::Dhcpv6DnsServerSource {
538 source_interface: Some(self.interface_id),
539 ..Default::default()
540 },
541 )),
542 ..Default::default()
543 }
544 })
545 .collect();
546 responder
547 .send(&response)
548 .map_err(ClientError::Fidl)?;
550 self.last_observed_dns_hash = hash;
551 Ok(())
552 }
553
554 fn schedule_timer(
559 &mut self,
560 timer_type: dhcpv6_core::client::ClientTimerType,
561 MonotonicInstant(instant): MonotonicInstant,
562 ) {
563 let timers = self.timers.as_mut().project();
564 let timer = match timer_type {
565 dhcpv6_core::client::ClientTimerType::Retransmission => timers.retransmission,
566 dhcpv6_core::client::ClientTimerType::Refresh => timers.refresh,
567 dhcpv6_core::client::ClientTimerType::Renew => timers.renew,
568 dhcpv6_core::client::ClientTimerType::Rebind => timers.rebind,
569 dhcpv6_core::client::ClientTimerType::RestartServerDiscovery => {
570 timers.restart_server_discovery
571 }
572 };
573 #[cfg(test)]
574 let _: bool = if instant == zx::MonotonicInstant::INFINITE {
575 timers.scheduled.remove(&timer_type)
576 } else {
577 timers.scheduled.insert(timer_type)
578 };
579 timer.reset(fasync::MonotonicInstant::from_zx(instant));
580 }
581
582 fn cancel_timer(&mut self, timer_type: dhcpv6_core::client::ClientTimerType) {
587 self.schedule_timer(timer_type, MonotonicInstant(zx::MonotonicInstant::INFINITE))
588 }
589
590 async fn handle_timeout(
592 &mut self,
593 timer_type: dhcpv6_core::client::ClientTimerType,
594 ) -> Result<(), ClientError> {
595 self.cancel_timer(timer_type);
597
598 let actions = self.state_machine.handle_timeout(timer_type, MonotonicInstant::now());
599 self.run_actions(actions).await
600 }
601
602 async fn handle_message_recv(&mut self, mut msg: &[u8]) -> Result<(), ClientError> {
604 let msg = match v6::Message::parse(&mut msg, ()) {
605 Ok(msg) => msg,
606 Err(e) => {
607 warn!("failed to parse received message: {}", e);
611 return Ok(());
612 }
613 };
614 let actions = self.state_machine.handle_message_receive(msg, MonotonicInstant::now());
615 self.run_actions(actions).await
616 }
617
618 fn handle_client_request(&mut self, request: ClientRequest) -> Result<(), ClientError> {
620 debug!("handling client request: {:?}", request);
621 match request {
622 ClientRequest::WatchServers { responder } => match self.dns_responder {
623 Some(_) => {
624 self.dns_responder = None;
626 Err(ClientError::DoubleWatch)
628 }
629 None => {
630 let dns_servers = self.state_machine.get_dns_servers();
631 let servers_hash = hash(&dns_servers);
632 if servers_hash != self.last_observed_dns_hash {
633 let () =
635 self.send_dns_server_updates(responder, dns_servers, servers_hash)?;
636 } else {
637 self.dns_responder = Some(responder);
639 }
640 Ok(())
641 }
642 },
643 ClientRequest::WatchAddress { responder } => match self.address_responder.take() {
644 Some(ClientWatchAddressResponder { .. }) => Err(ClientError::DoubleWatch),
646 None => {
647 warn!("WatchAddress call will block forever as it is unimplemented");
649 self.address_responder = Some(responder);
650 Ok(())
651 }
652 },
653 ClientRequest::WatchPrefixes { responder } => match self.prefixes_responder.take() {
654 Some(ClientWatchPrefixesResponder { .. }) => Err(ClientError::DoubleWatch),
656 None => {
657 self.prefixes_responder = Some(responder);
658 self.maybe_send_prefixes()
659 }
660 },
661 ClientRequest::Shutdown { responder: _ } => {
663 Err(ClientError::Unimplemented("Shutdown".to_string()))
664 }
665 }
666 }
667
668 async fn handle_next_event(&mut self, buf: &mut [u8]) -> Result<Option<()>, ClientError> {
674 let timers = self.timers.as_mut().project();
675 let timer_type = select! {
676 () = timers.retransmission => {
677 dhcpv6_core::client::ClientTimerType::Retransmission
678 },
679 () = timers.refresh => {
680 dhcpv6_core::client::ClientTimerType::Refresh
681 },
682 () = timers.renew => {
683 dhcpv6_core::client::ClientTimerType::Renew
684 },
685 () = timers.rebind => {
686 dhcpv6_core::client::ClientTimerType::Rebind
687 },
688 () = timers.restart_server_discovery => {
689 dhcpv6_core::client::ClientTimerType::RestartServerDiscovery
690 },
691 recv_from_res = self.socket.recv_from(buf).fuse() => {
692 let (size, _addr) = recv_from_res.map_err(ClientError::SocketRecv)?;
693 self.handle_message_recv(&buf[..size]).await?;
694 return Ok(Some(()));
695 },
696 request = self.request_stream.try_next() => {
697 let request = request.map_err(ClientError::Fidl)?;
698 return request.map(|request| self.handle_client_request(request)).transpose();
699 }
700 };
701 self.handle_timeout(timer_type).await?;
702 Ok(Some(()))
703 }
704
705 #[cfg(test)]
706 fn assert_scheduled(
707 &self,
708 timers: impl IntoIterator<Item = dhcpv6_core::client::ClientTimerType>,
709 ) {
710 assert_eq!(self.timers.as_ref().scheduled, timers.into_iter().collect())
711 }
712}
713
714fn create_socket(
717 addr: SocketAddr,
718 interface_id: fnet::InterfaceId,
719) -> std::io::Result<fasync::net::UdpSocket> {
720 let socket = socket2::Socket::new(
721 socket2::Domain::IPV6,
722 socket2::Type::DGRAM,
723 Some(socket2::Protocol::UDP),
724 )?;
725 socket.set_reuse_port(true)?;
727
728 let interface_id = u32::try_from(interface_id).map_err(|_| {
730 std::io::Error::new(std::io::ErrorKind::InvalidInput, "interface ID does not fit in u32")
731 })?;
732 let name = fuchsia_nix::net::if_::if_indextoname(interface_id)
733 .map_err(Into::<std::io::Error>::into)?;
734 socket.bind_device(Some(name.as_bytes()))?;
735
736 socket.bind(&addr.into())?;
737 fasync::net::UdpSocket::from_socket(socket.into())
738}
739
740fn duid_from_fidl(duid: Duid) -> Result<dhcpv6_core::ClientDuid, ()> {
741 const DUID_TYPE_LLT: [u8; 2] = [0, 1];
745 const DUID_TYPE_LL: [u8; 2] = [0, 3];
749 const DUID_TYPE_UUID: [u8; 2] = [0, 4];
753 const HARDWARE_TYPE_ETHERNET: [u8; 2] = [0, 1];
759 match duid {
760 Duid::LinkLayerAddressPlusTime(LinkLayerAddressPlusTime {
765 time,
766 link_layer_address: LinkLayerAddress::Ethernet(mac),
767 }) => {
768 let mut duid = dhcpv6_core::ClientDuid::new();
769 duid.try_extend_from_slice(&DUID_TYPE_LLT).unwrap();
770 duid.try_extend_from_slice(&HARDWARE_TYPE_ETHERNET).unwrap();
771 duid.write_u32::<NetworkEndian>(time).unwrap();
772 duid.try_extend_from_slice(&mac.octets).unwrap();
773 Ok(duid)
774 }
775 Duid::LinkLayerAddress(LinkLayerAddress::Ethernet(mac)) => Ok(DUID_TYPE_LL
779 .into_iter()
780 .chain(HARDWARE_TYPE_ETHERNET.into_iter())
781 .chain(mac.octets.into_iter())
782 .collect()),
783 Duid::Uuid(uuid) => Ok(DUID_TYPE_UUID.into_iter().chain(uuid.into_iter()).collect()),
786 _ => Err(()),
787 }
788}
789
790pub(crate) async fn serve_client(
794 NewClientParams { interface_id, address, duid, config }: NewClientParams,
795 request: ServerEnd<ClientMarker>,
796) -> Result<()> {
797 let std_addr = Ipv6Addr::from(address.address.addr);
798 if std_addr.is_multicast()
799 || (std_addr.is_unicast_link_local() && address.zone_index != interface_id)
800 {
801 return request
802 .close_with_epitaph(zx::Status::INVALID_ARGS)
803 .context("closing request channel with epitaph");
804 }
805
806 let fnet_ext::SocketAddress(addr) = fnet::SocketAddress::Ipv6(address).into();
807 let servers_addr = IpAddr::from_str(RELAY_AGENT_AND_SERVER_LINK_LOCAL_MULTICAST_ADDRESS)
808 .with_context(|| {
809 format!(
810 "{} should be a valid IPv6 address",
811 RELAY_AGENT_AND_SERVER_LINK_LOCAL_MULTICAST_ADDRESS,
812 )
813 })?;
814 let duid = match duid.map(|fidl| duid_from_fidl(fidl)).transpose() {
815 Ok(duid) => duid,
816 Err(()) => {
817 return request
818 .close_with_epitaph(zx::Status::INVALID_ARGS)
819 .context("closing request channel with epitaph");
820 }
821 };
822 let (request_stream, control_handle) = request.into_stream_and_control_handle();
823 let mut client = match Client::<fasync::net::UdpSocket>::start(
824 duid,
825 dhcpv6_core::client::transaction_id(),
826 config,
827 interface_id,
828 || create_socket(addr, interface_id),
829 SocketAddr::new(servers_addr, RELAY_AGENT_AND_SERVER_PORT),
830 request_stream,
831 )
832 .await
833 {
834 Ok(client) => client,
835 Err(ClientError::UnsupportedConfigs) => {
836 control_handle.shutdown_with_epitaph(zx::Status::INVALID_ARGS);
837 return Ok(());
838 }
839 Err(e) => {
840 return Err(e.into());
841 }
842 };
843 let mut buf = vec![0u8; MAX_UDP_DATAGRAM_SIZE];
844 loop {
845 match client.handle_next_event(&mut buf).await? {
846 Some(()) => (),
847 None => break Ok(()),
848 }
849 }
850}
851
852#[cfg(test)]
853mod tests {
854 use std::pin::pin;
855 use std::task::Poll;
856
857 use fidl::endpoints::{
858 ClientEnd, create_proxy, create_proxy_and_stream, create_request_stream,
859 };
860 use fidl_fuchsia_net_dhcpv6::{self as fnet_dhcpv6, ClientProxy, DEFAULT_CLIENT_PORT};
861 use fuchsia_async as fasync;
862 use futures::{TryFutureExt as _, join, poll};
863
864 use assert_matches::assert_matches;
865 use net_declare::{
866 fidl_ip_v6, fidl_ip_v6_with_prefix, fidl_mac, fidl_socket_addr, fidl_socket_addr_v6,
867 net_ip_v6, net_subnet_v6, std_socket_addr,
868 };
869 use net_types::ip::IpAddress as _;
870 use packet::serialize::InnerPacketBuilder;
871 use test_case::test_case;
872
873 use super::*;
874
875 fn create_test_socket() -> (fasync::net::UdpSocket, SocketAddr) {
877 let addr: SocketAddr = std_socket_addr!("[::1]:0");
878 let socket = std::net::UdpSocket::bind(addr).expect("failed to create test socket");
879 let addr = socket.local_addr().expect("failed to get address of test socket");
880 (fasync::net::UdpSocket::from_socket(socket).expect("failed to create test socket"), addr)
881 }
882
883 struct ReceivedMessage {
884 transaction_id: [u8; 3],
885 client_id: Option<Vec<u8>>,
901 }
902
903 async fn assert_received_message(
906 socket: &fasync::net::UdpSocket,
907 want_from_addr: SocketAddr,
908 msg_type: v6::MessageType,
909 ) -> ReceivedMessage {
910 let mut buf = vec![0u8; MAX_UDP_DATAGRAM_SIZE];
911 let (size, from_addr) =
912 socket.recv_from(&mut buf).await.expect("failed to receive on test server socket");
913 assert_eq!(from_addr, want_from_addr);
914 let buf = &mut &buf[..size]; let msg = v6::Message::parse(buf, ()).expect("failed to parse message");
916 assert_eq!(msg.msg_type(), msg_type);
917
918 let mut client_id = None;
919 for opt in msg.options() {
920 match opt {
921 v6::ParsedDhcpOption::ClientId(id) => {
922 assert_eq!(core::mem::replace(&mut client_id, Some(id.to_vec())), None)
923 }
924 _ => {}
925 }
926 }
927
928 ReceivedMessage { transaction_id: *msg.transaction_id(), client_id: client_id }
929 }
930
931 const TEST_MAC: fnet::MacAddress = fidl_mac!("00:01:02:03:04:05");
932
933 #[test_case(
934 Duid::LinkLayerAddress(LinkLayerAddress::Ethernet(TEST_MAC)),
935 &[0, 3, 0, 1, 0, 1, 2, 3, 4, 5];
936 "ll"
937 )]
938 #[test_case(
939 Duid::LinkLayerAddressPlusTime(LinkLayerAddressPlusTime {
940 time: 0,
941 link_layer_address: LinkLayerAddress::Ethernet(TEST_MAC),
942 }),
943 &[0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5];
944 "llt"
945 )]
946 #[test_case(
947 Duid::Uuid([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
948 &[0, 4, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
949 "uuid"
950 )]
951 #[fuchsia::test]
952 fn test_duid_from_fidl(duid: Duid, want: &[u8]) {
953 assert_eq!(duid_from_fidl(duid), Ok(dhcpv6_core::ClientDuid::try_from(want).unwrap()));
954 }
955
956 #[fuchsia::test]
957 fn test_create_client_with_unsupported_config() {
958 let prefix_delegation_configs = [
959 None,
960 Some(PrefixDelegationConfig::PrefixLength(0)),
962 Some(PrefixDelegationConfig::PrefixLength(Ipv6Addr::BYTES * 8 + 1)),
964 Some(PrefixDelegationConfig::Prefix(fidl_ip_v6_with_prefix!("::/64"))),
966 Some(PrefixDelegationConfig::Prefix(fidl_ip_v6_with_prefix!("a::1/64"))),
968 ];
969
970 for prefix_delegation_config in prefix_delegation_configs.iter() {
971 assert_matches!(
972 create_state_machine(
973 prefix_delegation_config.is_some().then(|| CLIENT_ID.into()),
974 [1, 2, 3],
975 ClientConfig {
976 information_config: Default::default(),
977 non_temporary_address_config: Default::default(),
978 prefix_delegation_config: prefix_delegation_config.clone(),
979 }
980 ),
981 Err(ClientError::UnsupportedConfigs),
982 "prefix_delegation_config={:?}",
983 prefix_delegation_config
984 );
985 }
986 }
987
988 const STATELESS_CLIENT_CONFIG: ClientConfig = ClientConfig {
989 information_config: InformationConfig { dns_servers: true },
990 non_temporary_address_config: AddressConfig { address_count: 0, preferred_addresses: None },
991 prefix_delegation_config: None,
992 };
993
994 #[fuchsia::test]
995 async fn test_client_stops_on_channel_close() {
996 let (client_proxy, server_end) = create_proxy::<ClientMarker>();
997
998 let ((), client_res) = join!(
999 async { drop(client_proxy) },
1000 serve_client(
1001 NewClientParams {
1002 interface_id: 1,
1003 address: fidl_socket_addr_v6!("[::1]:546"),
1004 config: STATELESS_CLIENT_CONFIG,
1005 duid: None,
1006 },
1007 server_end,
1008 ),
1009 );
1010 client_res.expect("client future should return with Ok");
1011 }
1012
1013 fn client_proxy_watch_servers(
1014 client_proxy: &fnet_dhcpv6::ClientProxy,
1015 ) -> impl Future<Output = Result<(), fidl::Error>> {
1016 client_proxy.watch_servers().map_ok(|_: Vec<fidl_fuchsia_net_name::DnsServer_>| ())
1017 }
1018
1019 fn client_proxy_watch_address(
1020 client_proxy: &fnet_dhcpv6::ClientProxy,
1021 ) -> impl Future<Output = Result<(), fidl::Error>> {
1022 client_proxy.watch_address().map_ok(
1023 |_: (
1024 fnet::Subnet,
1025 fidl_fuchsia_net_interfaces_admin::AddressParameters,
1026 fidl::endpoints::ServerEnd<
1027 fidl_fuchsia_net_interfaces_admin::AddressStateProviderMarker,
1028 >,
1029 )| (),
1030 )
1031 }
1032
1033 fn client_proxy_watch_prefixes(
1034 client_proxy: &fnet_dhcpv6::ClientProxy,
1035 ) -> impl Future<Output = Result<(), fidl::Error>> {
1036 client_proxy.watch_prefixes().map_ok(|_: Vec<fnet_dhcpv6::Prefix>| ())
1037 }
1038
1039 #[test_case(client_proxy_watch_servers; "watch_servers")]
1040 #[test_case(client_proxy_watch_address; "watch_address")]
1041 #[test_case(client_proxy_watch_prefixes; "watch_prefixes")]
1042 #[fuchsia::test]
1043 async fn test_client_should_return_error_on_double_watch<F>(watch: F)
1044 where
1045 F: AsyncFn(&fnet_dhcpv6::ClientProxy) -> Result<(), fidl::Error>,
1046 {
1047 let (client_proxy, server_end) = create_proxy::<ClientMarker>();
1048
1049 let (caller1_res, caller2_res, client_res) = join!(
1050 watch(&client_proxy),
1051 watch(&client_proxy),
1052 serve_client(
1053 NewClientParams {
1054 interface_id: 1,
1055 address: fidl_socket_addr_v6!("[::1]:546"),
1056 config: STATELESS_CLIENT_CONFIG,
1057 duid: None,
1058 },
1059 server_end,
1060 )
1061 );
1062
1063 assert_matches!(
1064 caller1_res,
1065 Err(fidl::Error::ClientChannelClosed { status: zx::Status::PEER_CLOSED, .. })
1066 );
1067 assert_matches!(
1068 caller2_res,
1069 Err(fidl::Error::ClientChannelClosed { status: zx::Status::PEER_CLOSED, .. })
1070 );
1071 assert!(
1072 client_res
1073 .expect_err("client should fail with double watch error")
1074 .to_string()
1075 .contains("got watch request while the previous one is pending")
1076 );
1077 }
1078
1079 const VALID_INFORMATION_CONFIGS: [InformationConfig; 2] =
1080 [InformationConfig { dns_servers: false }, InformationConfig { dns_servers: true }];
1081
1082 const VALID_DELEGATED_PREFIX_CONFIGS: [Option<PrefixDelegationConfig>; 4] = [
1083 Some(PrefixDelegationConfig::Empty(Empty {})),
1084 Some(PrefixDelegationConfig::PrefixLength(1)),
1085 Some(PrefixDelegationConfig::PrefixLength(127)),
1086 Some(PrefixDelegationConfig::Prefix(fidl_ip_v6_with_prefix!("a::/64"))),
1087 ];
1088
1089 fn get_valid_non_temporary_address_configs() -> [AddressConfig; 5] {
1091 [
1092 Default::default(),
1093 AddressConfig { address_count: 1, preferred_addresses: None },
1094 AddressConfig { address_count: 1, preferred_addresses: Some(Vec::new()) },
1095 AddressConfig {
1096 address_count: 1,
1097 preferred_addresses: Some(vec![fidl_ip_v6!("a::1")]),
1098 },
1099 AddressConfig {
1100 address_count: 2,
1101 preferred_addresses: Some(vec![fidl_ip_v6!("a::2")]),
1102 },
1103 ]
1104 }
1105
1106 #[fuchsia::test]
1107 fn test_client_starts_with_valid_args() {
1108 for information_config in VALID_INFORMATION_CONFIGS {
1109 for non_temporary_address_config in get_valid_non_temporary_address_configs() {
1110 for prefix_delegation_config in VALID_DELEGATED_PREFIX_CONFIGS {
1111 let mut exec = fasync::TestExecutor::new();
1112
1113 let (client_proxy, server_end) = create_proxy::<ClientMarker>();
1114
1115 let test_fut = async {
1116 join!(
1117 client_proxy.watch_servers(),
1118 serve_client(
1119 NewClientParams {
1120 interface_id: 1,
1121 address: fidl_socket_addr_v6!("[::1]:546"),
1122 config: ClientConfig {
1123 information_config: information_config.clone(),
1124 non_temporary_address_config: non_temporary_address_config
1125 .clone(),
1126 prefix_delegation_config: prefix_delegation_config.clone(),
1127 },
1128 duid: (non_temporary_address_config.address_count != 0
1129 || prefix_delegation_config.is_some())
1130 .then(|| fnet_dhcpv6::Duid::LinkLayerAddress(
1131 fnet_dhcpv6::LinkLayerAddress::Ethernet(fidl_mac!(
1132 "00:11:22:33:44:55"
1133 ))
1134 )),
1135 },
1136 server_end
1137 )
1138 )
1139 };
1140 let mut test_fut = pin!(test_fut);
1141 assert_matches!(
1142 exec.run_until_stalled(&mut test_fut),
1143 Poll::Pending,
1144 "information_config={:?}, non_temporary_address_config={:?}, prefix_delegation_config={:?}",
1145 information_config,
1146 non_temporary_address_config,
1147 prefix_delegation_config
1148 );
1149 }
1150 }
1151 }
1152 }
1153
1154 const CLIENT_ID: [u8; 18] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17];
1155
1156 #[fuchsia::test]
1157 async fn test_client_starts_in_correct_mode() {
1158 for information_config @ InformationConfig { dns_servers } in VALID_INFORMATION_CONFIGS {
1159 for non_temporary_address_config @ AddressConfig {
1160 address_count,
1161 preferred_addresses: _,
1162 } in get_valid_non_temporary_address_configs()
1163 {
1164 for prefix_delegation_config in VALID_DELEGATED_PREFIX_CONFIGS {
1165 let (stateful, want_msg_type) =
1166 if address_count == 0 && prefix_delegation_config.is_none() {
1167 if !dns_servers {
1168 continue;
1169 } else {
1170 (false, v6::MessageType::InformationRequest)
1171 }
1172 } else {
1173 (true, v6::MessageType::Solicit)
1174 };
1175
1176 let (_, client_stream): (ClientEnd<ClientMarker>, _) =
1177 create_request_stream::<ClientMarker>();
1178
1179 let (client_socket, client_addr) = create_test_socket();
1180 let (server_socket, server_addr) = create_test_socket();
1181 println!(
1182 "{:?} {:?} {:?}",
1183 information_config, non_temporary_address_config, prefix_delegation_config
1184 );
1185 let _: Client<fasync::net::UdpSocket> = Client::start(
1186 stateful.then(|| CLIENT_ID.into()),
1187 [1, 2, 3], ClientConfig {
1189 information_config: information_config.clone(),
1190 non_temporary_address_config: non_temporary_address_config.clone(),
1191 prefix_delegation_config: prefix_delegation_config.clone(),
1192 },
1193 1, || Ok(client_socket),
1195 server_addr,
1196 client_stream,
1197 )
1198 .await
1199 .unwrap_or_else(|e| panic!(
1200 "failed to create test client: {}; information_config={:?}, non_temporary_address_config={:?}, prefix_delegation_config={:?}",
1201 e, information_config, non_temporary_address_config, prefix_delegation_config
1202 ));
1203
1204 let _: ReceivedMessage =
1205 assert_received_message(&server_socket, client_addr, want_msg_type).await;
1206 }
1207 }
1208 }
1209 }
1210
1211 #[fuchsia::test]
1214 async fn test_client_fails_to_start_with_invalid_args() {
1215 for params in vec![
1216 NewClientParams {
1218 interface_id: 2,
1219 address: fnet::Ipv6SocketAddress {
1220 address: fidl_ip_v6!("fe80::1"),
1221 port: DEFAULT_CLIENT_PORT,
1222 zone_index: 1,
1223 },
1224 config: STATELESS_CLIENT_CONFIG,
1225 duid: None,
1226 },
1227 NewClientParams {
1229 interface_id: 1,
1230 address: fnet::Ipv6SocketAddress {
1231 address: fidl_ip_v6!("ff01::1"),
1232 port: DEFAULT_CLIENT_PORT,
1233 zone_index: 1,
1234 },
1235 config: STATELESS_CLIENT_CONFIG,
1236 duid: None,
1237 },
1238 NewClientParams {
1240 interface_id: 1,
1241 address: fidl_socket_addr_v6!("[2001:db8::1]:12345"),
1242 config: STATELESS_CLIENT_CONFIG,
1243 duid: Some(fnet_dhcpv6::Duid::LinkLayerAddress(
1244 fnet_dhcpv6::LinkLayerAddress::Ethernet(fidl_mac!("00:11:22:33:44:55")),
1245 )),
1246 },
1247 NewClientParams {
1249 interface_id: 1,
1250 address: fidl_socket_addr_v6!("[2001:db8::1]:12345"),
1251 config: ClientConfig {
1252 information_config: InformationConfig { dns_servers: true },
1253 non_temporary_address_config: AddressConfig {
1254 address_count: 1,
1255 preferred_addresses: None,
1256 },
1257 prefix_delegation_config: None,
1258 },
1259 duid: None,
1260 },
1261 ] {
1262 let (client_proxy, server_end) = create_proxy::<ClientMarker>();
1263 let () =
1264 serve_client(params, server_end).await.expect("start server failed unexpectedly");
1265 assert_matches!(
1268 client_proxy.watch_servers().await,
1269 Err(fidl::Error::ClientChannelClosed { status: zx::Status::INVALID_ARGS, .. })
1270 );
1271 }
1272 }
1273
1274 fn create_test_dns_server(
1275 address: fnet::Ipv6Address,
1276 source_interface: u64,
1277 zone_index: u64,
1278 ) -> fnet_name::DnsServer_ {
1279 fnet_name::DnsServer_ {
1280 address: Some(fnet::SocketAddress::Ipv6(fnet::Ipv6SocketAddress {
1281 address,
1282 zone_index,
1283 port: DEFAULT_DNS_PORT,
1284 })),
1285 source: Some(fnet_name::DnsServerSource::Dhcpv6(fnet_name::Dhcpv6DnsServerSource {
1286 source_interface: Some(source_interface),
1287 ..Default::default()
1288 })),
1289 ..Default::default()
1290 }
1291 }
1292
1293 async fn send_msg_with_options(
1294 socket: &fasync::net::UdpSocket,
1295 to_addr: SocketAddr,
1296 transaction_id: [u8; 3],
1297 msg_type: v6::MessageType,
1298 options: &[v6::DhcpOption<'_>],
1299 ) -> Result<()> {
1300 let builder = v6::MessageBuilder::new(msg_type, transaction_id, options);
1301 let mut buf = vec![0u8; builder.bytes_len()];
1302 builder.serialize(&mut buf);
1303 let size = socket.send_to(&buf, to_addr).await?;
1304 assert_eq!(size, buf.len());
1305 Ok(())
1306 }
1307
1308 #[fuchsia::test]
1309 fn test_client_should_respond_to_dns_watch_requests() {
1310 let mut exec = fasync::TestExecutor::new();
1311 let transaction_id = [1, 2, 3];
1312
1313 let (client_proxy, client_stream) = create_proxy_and_stream::<ClientMarker>();
1314
1315 let (client_socket, client_addr) = create_test_socket();
1316 let (server_socket, server_addr) = create_test_socket();
1317 let mut client = exec
1318 .run_singlethreaded(Client::<fasync::net::UdpSocket>::start(
1319 None,
1320 transaction_id,
1321 STATELESS_CLIENT_CONFIG,
1322 1, || Ok(client_socket),
1324 server_addr,
1325 client_stream,
1326 ))
1327 .expect("failed to create test client");
1328
1329 type WatchServersResponseFut = <fnet_dhcpv6::ClientProxy as fnet_dhcpv6::ClientProxyInterface>::WatchServersResponseFut;
1330 type WatchServersResponse = <WatchServersResponseFut as Future>::Output;
1331
1332 struct Test<'a> {
1333 client: &'a mut Client<fasync::net::UdpSocket>,
1334 buf: Vec<u8>,
1335 watcher_fut: WatchServersResponseFut,
1336 }
1337
1338 impl<'a> Test<'a> {
1339 fn new(
1340 client: &'a mut Client<fasync::net::UdpSocket>,
1341 client_proxy: &ClientProxy,
1342 ) -> Self {
1343 Self {
1344 client,
1345 buf: vec![0u8; MAX_UDP_DATAGRAM_SIZE],
1346 watcher_fut: client_proxy.watch_servers(),
1347 }
1348 }
1349
1350 async fn handle_next_event(&mut self) {
1351 self.client
1352 .handle_next_event(&mut self.buf)
1353 .await
1354 .expect("test client failed to handle next event")
1355 .expect("request stream closed");
1356 }
1357
1358 async fn refresh_client(&mut self) {
1359 if self
1363 .client
1364 .timers
1365 .as_ref()
1366 .scheduled
1367 .contains(&dhcpv6_core::client::ClientTimerType::Refresh)
1368 {
1369 self.client
1370 .handle_timeout(dhcpv6_core::client::ClientTimerType::Refresh)
1371 .await
1372 .expect("test client failed to handle timeout");
1373 } else {
1374 panic!("no refresh timer is scheduled and refresh is requested in test");
1375 }
1376 }
1377
1378 fn run(&mut self) -> impl Future<Output = WatchServersResponse> + use<'_, 'a> {
1382 let Self { client, buf, watcher_fut } = self;
1383 async move {
1384 let client_fut = async {
1385 loop {
1386 client
1387 .handle_next_event(buf)
1388 .await
1389 .expect("test client failed to handle next event")
1390 .expect("request stream closed");
1391 }
1392 }
1393 .fuse();
1394 let mut client_fut = pin!(client_fut);
1395 let mut watcher_fut = watcher_fut.fuse();
1396 select! {
1397 () = client_fut => panic!("test client returned unexpectedly"),
1398 r = watcher_fut => r,
1399 }
1400 }
1401 }
1402 }
1403
1404 {
1405 let mut test = Test::new(&mut client, &client_proxy);
1407
1408 exec.run_singlethreaded(test.handle_next_event());
1410 assert!(
1411 test.client.dns_responder.is_some(),
1412 "WatchServers responder should be present"
1413 );
1414
1415 exec.run_singlethreaded(send_msg_with_options(
1417 &server_socket,
1418 client_addr,
1419 transaction_id,
1420 v6::MessageType::Reply,
1421 &[v6::DhcpOption::ServerId(&[1, 2, 3]), v6::DhcpOption::DnsServers(&[])],
1422 ))
1423 .expect("failed to send test reply");
1424 exec.run_singlethreaded(test.handle_next_event());
1430 assert_matches!(exec.run_until_stalled(&mut pin!(test.run())), Poll::Pending);
1431
1432 exec.run_singlethreaded(test.refresh_client());
1434 let dns_servers = [net_ip_v6!("fe80::1:2")];
1435 exec.run_singlethreaded(send_msg_with_options(
1436 &server_socket,
1437 client_addr,
1438 transaction_id,
1439 v6::MessageType::Reply,
1440 &[v6::DhcpOption::ServerId(&[1, 2, 3]), v6::DhcpOption::DnsServers(&dns_servers)],
1441 ))
1442 .expect("failed to send test reply");
1443 let want_servers = vec![create_test_dns_server(
1444 fidl_ip_v6!("fe80::1:2"),
1445 1, 1, )];
1448 let servers = exec.run_singlethreaded(test.run()).expect("get servers");
1449 assert_eq!(servers, want_servers);
1450 } {
1453 let mut test = Test::new(&mut client, &client_proxy);
1455
1456 exec.run_singlethreaded(test.handle_next_event());
1458 assert!(
1459 test.client.dns_responder.is_some(),
1460 "WatchServers responder should be present"
1461 );
1462
1463 exec.run_singlethreaded(test.refresh_client());
1465 let dns_servers = [net_ip_v6!("fe80::1:2")];
1466 exec.run_singlethreaded(send_msg_with_options(
1467 &server_socket,
1468 client_addr,
1469 transaction_id,
1470 v6::MessageType::Reply,
1471 &[v6::DhcpOption::ServerId(&[1, 2, 3]), v6::DhcpOption::DnsServers(&dns_servers)],
1472 ))
1473 .expect("failed to send test reply");
1474 exec.run_singlethreaded(test.handle_next_event());
1480 assert_matches!(exec.run_until_stalled(&mut pin!(test.run())), Poll::Pending);
1481
1482 exec.run_singlethreaded(test.refresh_client());
1484 let dns_servers = [net_ip_v6!("fe80::1:2"), net_ip_v6!("1234::5:6")];
1485 exec.run_singlethreaded(send_msg_with_options(
1486 &server_socket,
1487 client_addr,
1488 transaction_id,
1489 v6::MessageType::Reply,
1490 &[v6::DhcpOption::ServerId(&[1, 2, 3]), v6::DhcpOption::DnsServers(&dns_servers)],
1491 ))
1492 .expect("failed to send test reply");
1493 let want_servers = vec![
1494 create_test_dns_server(
1495 fidl_ip_v6!("fe80::1:2"),
1496 1, 1, ),
1499 create_test_dns_server(
1501 fidl_ip_v6!("1234::5:6"),
1502 1, 0, ),
1505 ];
1506 let servers = exec.run_singlethreaded(test.run()).expect("get servers");
1507 assert_eq!(servers, want_servers);
1508 } {
1511 let mut test = Test::new(&mut client, &client_proxy);
1515
1516 exec.run_singlethreaded(test.refresh_client());
1517 exec.run_singlethreaded(send_msg_with_options(
1518 &server_socket,
1519 client_addr,
1520 transaction_id,
1521 v6::MessageType::Reply,
1522 &[v6::DhcpOption::ServerId(&[1, 2, 3]), v6::DhcpOption::DnsServers(&[])],
1523 ))
1524 .expect("failed to send test reply");
1525 let want_servers = Vec::<fnet_name::DnsServer_>::new();
1526 assert_eq!(exec.run_singlethreaded(test.run()).expect("get servers"), want_servers);
1527 } }
1529
1530 #[fuchsia::test]
1531 async fn test_client_should_respond_with_dns_servers_on_first_watch_if_non_empty() {
1532 let transaction_id = [1, 2, 3];
1533
1534 let (client_proxy, client_stream) = create_proxy_and_stream::<ClientMarker>();
1535
1536 let (client_socket, client_addr) = create_test_socket();
1537 let (server_socket, server_addr) = create_test_socket();
1538 let client = Client::<fasync::net::UdpSocket>::start(
1539 None,
1540 transaction_id,
1541 STATELESS_CLIENT_CONFIG,
1542 1, || Ok(client_socket),
1544 server_addr,
1545 client_stream,
1546 )
1547 .await
1548 .expect("failed to create test client");
1549
1550 let dns_servers = [net_ip_v6!("fe80::1:2"), net_ip_v6!("1234::5:6")];
1551 send_msg_with_options(
1552 &server_socket,
1553 client_addr,
1554 transaction_id,
1555 v6::MessageType::Reply,
1556 &[v6::DhcpOption::ServerId(&[4, 5, 6]), v6::DhcpOption::DnsServers(&dns_servers)],
1557 )
1558 .await
1559 .expect("failed to send test message");
1560
1561 let buf = vec![0u8; MAX_UDP_DATAGRAM_SIZE];
1562 let handle_client_events_fut =
1563 futures::stream::try_unfold((client, buf), |(mut client, mut buf)| async {
1564 client
1565 .handle_next_event(&mut buf)
1566 .await
1567 .map(|res| res.map(|()| ((), (client, buf))))
1568 })
1569 .try_fold((), |(), ()| futures::future::ready(Ok(())))
1570 .fuse();
1571 let mut handle_client_events_fut = pin!(handle_client_events_fut);
1572
1573 let want_servers = vec![
1574 create_test_dns_server(
1575 fidl_ip_v6!("fe80::1:2"),
1576 1, 1, ),
1579 create_test_dns_server(
1580 fidl_ip_v6!("1234::5:6"),
1581 1, 0, ),
1584 ];
1585 let found_servers = select!(
1586 status = handle_client_events_fut => panic!("client unexpectedly exited: {status:?}"),
1587 found_servers = client_proxy.watch_servers() => found_servers.expect(
1588 "watch servers should succeed"),
1589 );
1590 assert_eq!(found_servers, want_servers);
1591 }
1592
1593 #[fuchsia::test]
1594 async fn watch_prefixes() {
1595 const SERVER_ID: [u8; 3] = [3, 4, 5];
1596 const PREFERRED_LIFETIME_SECS: u32 = 1000;
1597 const VALID_LIFETIME_SECS: u32 = 2000;
1598 const T1: u32 = 1;
1601 const T2: u32 = 2000;
1602
1603 let (client_proxy, client_stream) = create_proxy_and_stream::<ClientMarker>();
1604
1605 let (client_socket, client_addr) = create_test_socket();
1606 let (server_socket, server_addr) = create_test_socket();
1607 let mut client = Client::<fasync::net::UdpSocket>::start(
1608 Some(CLIENT_ID.into()),
1609 [1, 2, 3],
1610 ClientConfig {
1611 information_config: Default::default(),
1612 non_temporary_address_config: Default::default(),
1613 prefix_delegation_config: Some(PrefixDelegationConfig::Empty(Empty {})),
1614 },
1615 1, || Ok(client_socket),
1617 server_addr,
1618 client_stream,
1619 )
1620 .await
1621 .expect("failed to create test client");
1622
1623 let client_fut = async {
1624 let mut buf = vec![0u8; MAX_UDP_DATAGRAM_SIZE];
1625 loop {
1626 select! {
1627 res = client.handle_next_event(&mut buf).fuse() => {
1628 match res.expect("test client failed to handle next event") {
1629 Some(()) => (),
1630 None => break (),
1631 };
1632 }
1633 }
1634 }
1635 }
1636 .fuse();
1637 let mut client_fut = pin!(client_fut);
1638
1639 let update_prefix = net_subnet_v6!("a::/64");
1640 let remove_prefix = net_subnet_v6!("b::/64");
1641 let add_prefix = net_subnet_v6!("c::/64");
1642
1643 let client_id = {
1645 let ReceivedMessage { client_id, transaction_id } =
1646 assert_received_message(&server_socket, client_addr, v6::MessageType::Solicit)
1647 .await;
1648 let client_id = client_id.unwrap();
1650
1651 let ia_prefix = [
1652 v6::DhcpOption::IaPrefix(v6::IaPrefixSerializer::new(
1653 PREFERRED_LIFETIME_SECS,
1654 VALID_LIFETIME_SECS,
1655 update_prefix,
1656 &[],
1657 )),
1658 v6::DhcpOption::IaPrefix(v6::IaPrefixSerializer::new(
1659 PREFERRED_LIFETIME_SECS,
1660 VALID_LIFETIME_SECS,
1661 remove_prefix,
1662 &[],
1663 )),
1664 ];
1665 send_msg_with_options(
1666 &server_socket,
1667 client_addr,
1668 transaction_id,
1669 v6::MessageType::Advertise,
1670 &[
1671 v6::DhcpOption::ServerId(&SERVER_ID),
1672 v6::DhcpOption::ClientId(&client_id),
1673 v6::DhcpOption::Preference(u8::MAX),
1674 v6::DhcpOption::IaPd(v6::IaPdSerializer::new(IA_PD_IAID, T1, T2, &ia_prefix)),
1675 ],
1676 )
1677 .await
1678 .expect("failed to send adv message");
1679
1680 let transaction_id = select! {
1683 () = client_fut => panic!("should never return"),
1684 res = assert_received_message(
1685 &server_socket,
1686 client_addr,
1687 v6::MessageType::Request,
1688 ).fuse() => {
1689 let ReceivedMessage { client_id: req_client_id, transaction_id } = res;
1690 assert_eq!(Some(&client_id), req_client_id.as_ref());
1691 transaction_id
1692 },
1693 };
1694
1695 send_msg_with_options(
1696 &server_socket,
1697 client_addr,
1698 transaction_id,
1699 v6::MessageType::Reply,
1700 &[
1701 v6::DhcpOption::ServerId(&SERVER_ID),
1702 v6::DhcpOption::ClientId(&client_id),
1703 v6::DhcpOption::IaPd(v6::IaPdSerializer::new(IA_PD_IAID, T1, T2, &ia_prefix)),
1704 ],
1705 )
1706 .await
1707 .expect("failed to send reply message");
1708
1709 client_id
1710 };
1711
1712 let check_watch_prefixes_result =
1713 |res: Result<Vec<Prefix>, _>,
1714 before_handling_reply,
1715 preferred_lifetime_secs: u32,
1716 valid_lifetime_secs: u32,
1717 expected_prefixes| {
1718 assert_matches!(
1719 res.unwrap()[..],
1720 [
1721 Prefix {
1722 prefix: got_prefix1,
1723 lifetimes: Lifetimes {
1724 preferred_until: preferred_until1,
1725 valid_until: valid_until1,
1726 },
1727 },
1728 Prefix {
1729 prefix: got_prefix2,
1730 lifetimes: Lifetimes {
1731 preferred_until: preferred_until2,
1732 valid_until: valid_until2,
1733 },
1734 },
1735 ] => {
1736 let now = zx::MonotonicInstant::get();
1737 let preferred_until = zx::MonotonicInstant::from_nanos(preferred_until1);
1738 let valid_until = zx::MonotonicInstant::from_nanos(valid_until1);
1739
1740 let preferred_for = zx::MonotonicDuration::from_seconds(
1741 preferred_lifetime_secs.into(),
1742 );
1743 let valid_for = zx::MonotonicDuration::from_seconds(valid_lifetime_secs.into());
1744
1745 assert_eq!(
1746 HashSet::from([got_prefix1, got_prefix2]),
1747 HashSet::from(expected_prefixes),
1748 );
1749 assert!(preferred_until >= before_handling_reply + preferred_for);
1750 assert!(preferred_until <= now + preferred_for);
1751 assert!(valid_until >= before_handling_reply + valid_for);
1752 assert!(valid_until <= now + valid_for);
1753
1754 assert_eq!(preferred_until1, preferred_until2);
1755 assert_eq!(valid_until1, valid_until2);
1756 }
1757 )
1758 };
1759
1760 {
1763 let mut watch_prefixes = client_proxy.watch_prefixes().fuse();
1766 assert_matches!(poll!(&mut watch_prefixes), Poll::Pending);
1767 let before_handling_reply = zx::MonotonicInstant::get();
1768 select! {
1769 () = client_fut => panic!("should never return"),
1770 res = watch_prefixes => check_watch_prefixes_result(
1771 res,
1772 before_handling_reply,
1773 PREFERRED_LIFETIME_SECS,
1774 VALID_LIFETIME_SECS,
1775 [
1776 subnet_to_address_with_prefix(update_prefix),
1777 subnet_to_address_with_prefix(remove_prefix),
1778 ],
1779 ),
1780 }
1781 }
1782
1783 {
1786 let transaction_id = select! {
1787 () = client_fut => panic!("should never return"),
1788 res = assert_received_message(
1789 &server_socket,
1790 client_addr,
1791 v6::MessageType::Renew,
1792 ).fuse() => {
1793 let ReceivedMessage { client_id: ren_client_id, transaction_id } = res;
1794 assert_eq!(ren_client_id.as_ref(), Some(&client_id));
1795 transaction_id
1796 },
1797 };
1798
1799 const NEW_PREFERRED_LIFETIME_SECS: u32 = 2 * PREFERRED_LIFETIME_SECS;
1800 const NEW_VALID_LIFETIME_SECS: u32 = 2 * VALID_LIFETIME_SECS;
1801 let ia_prefix = [
1802 v6::DhcpOption::IaPrefix(v6::IaPrefixSerializer::new(
1803 NEW_PREFERRED_LIFETIME_SECS,
1804 NEW_VALID_LIFETIME_SECS,
1805 update_prefix,
1806 &[],
1807 )),
1808 v6::DhcpOption::IaPrefix(v6::IaPrefixSerializer::new(
1809 NEW_PREFERRED_LIFETIME_SECS,
1810 NEW_VALID_LIFETIME_SECS,
1811 add_prefix,
1812 &[],
1813 )),
1814 v6::DhcpOption::IaPrefix(v6::IaPrefixSerializer::new(0, 0, remove_prefix, &[])),
1815 ];
1816
1817 send_msg_with_options(
1818 &server_socket,
1819 client_addr,
1820 transaction_id,
1821 v6::MessageType::Reply,
1822 &[
1823 v6::DhcpOption::ServerId(&SERVER_ID),
1824 v6::DhcpOption::ClientId(&client_id),
1825 v6::DhcpOption::IaPd(v6::IaPdSerializer::new(
1826 v6::IAID::new(0),
1827 T1,
1828 T2,
1829 &ia_prefix,
1830 )),
1831 ],
1832 )
1833 .await
1834 .expect("failed to send reply message");
1835
1836 let before_handling_reply = zx::MonotonicInstant::get();
1837 select! {
1838 () = client_fut => panic!("should never return"),
1839 res = client_proxy.watch_prefixes().fuse() => check_watch_prefixes_result(
1840 res,
1841 before_handling_reply,
1842 NEW_PREFERRED_LIFETIME_SECS,
1843 NEW_VALID_LIFETIME_SECS,
1844 [
1845 subnet_to_address_with_prefix(update_prefix),
1846 subnet_to_address_with_prefix(add_prefix),
1847 ],
1848 ),
1849 }
1850 }
1851 }
1852
1853 #[fuchsia::test]
1854 async fn test_client_schedule_and_cancel_timers() {
1855 let (_client_end, client_stream) = create_request_stream::<ClientMarker>();
1856
1857 let (client_socket, _client_addr) = create_test_socket();
1858 let (_server_socket, server_addr) = create_test_socket();
1859 let mut client = Client::<fasync::net::UdpSocket>::start(
1860 None,
1861 [1, 2, 3], STATELESS_CLIENT_CONFIG,
1863 1, || Ok(client_socket),
1865 server_addr,
1866 client_stream,
1867 )
1868 .await
1869 .expect("failed to create test client");
1870
1871 client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Retransmission]);
1873
1874 client.cancel_timer(dhcpv6_core::client::ClientTimerType::Retransmission);
1875 client.assert_scheduled([]);
1876
1877 let now = MonotonicInstant::now();
1878 client.schedule_timer(
1879 dhcpv6_core::client::ClientTimerType::Refresh,
1880 now + Duration::from_nanos(1),
1881 );
1882 client.schedule_timer(
1883 dhcpv6_core::client::ClientTimerType::Retransmission,
1884 now + Duration::from_nanos(2),
1885 );
1886 client.assert_scheduled([
1887 dhcpv6_core::client::ClientTimerType::Retransmission,
1888 dhcpv6_core::client::ClientTimerType::Refresh,
1889 ]);
1890
1891 let now = MonotonicInstant::now();
1893 client.schedule_timer(
1894 dhcpv6_core::client::ClientTimerType::Refresh,
1895 now + Duration::from_nanos(1),
1896 );
1897 client.schedule_timer(
1898 dhcpv6_core::client::ClientTimerType::Retransmission,
1899 now + Duration::from_nanos(2),
1900 );
1901
1902 client.cancel_timer(dhcpv6_core::client::ClientTimerType::Refresh);
1903 client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Retransmission]);
1904
1905 client.cancel_timer(dhcpv6_core::client::ClientTimerType::Refresh);
1907
1908 client.cancel_timer(dhcpv6_core::client::ClientTimerType::Retransmission);
1909 client.assert_scheduled([]);
1910
1911 client.cancel_timer(dhcpv6_core::client::ClientTimerType::Retransmission);
1913 }
1914
1915 #[fuchsia::test]
1916 async fn test_handle_next_event_on_stateless_client() {
1917 let (client_proxy, client_stream) = create_proxy_and_stream::<ClientMarker>();
1918
1919 let (client_socket, client_addr) = create_test_socket();
1920 let (server_socket, server_addr) = create_test_socket();
1921 let mut client = Client::<fasync::net::UdpSocket>::start(
1922 None,
1923 [1, 2, 3], STATELESS_CLIENT_CONFIG,
1925 1, || Ok(client_socket),
1927 server_addr,
1928 client_stream,
1929 )
1930 .await
1931 .expect("failed to create test client");
1932
1933 let ReceivedMessage { client_id, transaction_id: _ } = assert_received_message(
1935 &server_socket,
1936 client_addr,
1937 v6::MessageType::InformationRequest,
1938 )
1939 .await;
1940 client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Retransmission]);
1941
1942 let mut buf = vec![0u8; MAX_UDP_DATAGRAM_SIZE];
1943 assert_matches!(client.handle_next_event(&mut buf).await, Ok(Some(())));
1945 let ReceivedMessage { client_id: got_client_id, transaction_id: _ } =
1946 assert_received_message(
1947 &server_socket,
1948 client_addr,
1949 v6::MessageType::InformationRequest,
1950 )
1951 .await;
1952 assert_eq!(got_client_id, client_id);
1953 client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Retransmission]);
1954
1955 send_msg_with_options(&server_socket, client_addr, [5, 6, 7], v6::MessageType::Reply, &[])
1957 .await
1958 .expect("failed to send test message");
1959 assert_matches!(client.handle_next_event(&mut buf).await, Ok(Some(())));
1960 client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Retransmission]);
1961
1962 let size =
1964 server_socket.send_to(&[], client_addr).await.expect("failed to send test message");
1965 assert_eq!(size, 0);
1966 assert_matches!(client.handle_next_event(&mut buf).await, Ok(Some(())));
1967 client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Retransmission]);
1968
1969 send_msg_with_options(
1971 &server_socket,
1972 client_addr,
1973 [1, 2, 3],
1974 v6::MessageType::Reply,
1975 &[v6::DhcpOption::ServerId(&[4, 5, 6])],
1976 )
1977 .await
1978 .expect("failed to send test message");
1979 assert_matches!(client.handle_next_event(&mut buf).await, Ok(Some(())));
1980 client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Refresh]);
1981
1982 client.schedule_timer(
1984 dhcpv6_core::client::ClientTimerType::Refresh,
1985 MonotonicInstant::now() + Duration::from_nanos(1),
1986 );
1987
1988 assert_matches!(client.handle_next_event(&mut buf).await, Ok(Some(())));
1990 let ReceivedMessage { client_id, transaction_id: _ } = assert_received_message(
1991 &server_socket,
1992 client_addr,
1993 v6::MessageType::InformationRequest,
1994 )
1995 .await;
1996 assert_eq!(got_client_id, client_id,);
1997 client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Retransmission]);
1998
1999 let test_fut = async {
2000 assert_matches!(client.handle_next_event(&mut buf).await, Ok(Some(())));
2001 client
2002 .dns_responder
2003 .take()
2004 .expect("test client did not get a channel responder")
2005 .send(&[fnet_name::DnsServer_ {
2006 address: Some(fidl_socket_addr!("[fe01::2:3]:42")),
2007 source: Some(fnet_name::DnsServerSource::Dhcpv6(
2008 fnet_name::Dhcpv6DnsServerSource {
2009 source_interface: Some(42),
2010 ..Default::default()
2011 },
2012 )),
2013 ..Default::default()
2014 }])
2015 .expect("failed to send response on test channel");
2016 };
2017 let (watcher_res, ()) = join!(client_proxy.watch_servers(), test_fut);
2018 let servers = watcher_res.expect("failed to watch servers");
2019 assert_eq!(
2020 servers,
2021 vec![fnet_name::DnsServer_ {
2022 address: Some(fidl_socket_addr!("[fe01::2:3]:42")),
2023 source: Some(fnet_name::DnsServerSource::Dhcpv6(
2024 fnet_name::Dhcpv6DnsServerSource {
2025 source_interface: Some(42),
2026 ..Default::default()
2027 },
2028 )),
2029 ..Default::default()
2030 }]
2031 );
2032
2033 drop(client_proxy);
2035 assert_matches!(client.handle_next_event(&mut buf).await, Ok(None));
2036 }
2037
2038 #[fuchsia::test]
2039 async fn test_handle_next_event_on_stateful_client() {
2040 let (client_proxy, client_stream) = create_proxy_and_stream::<ClientMarker>();
2041
2042 let (client_socket, client_addr) = create_test_socket();
2043 let (server_socket, server_addr) = create_test_socket();
2044 let mut client = Client::<fasync::net::UdpSocket>::start(
2045 Some(CLIENT_ID.into()),
2046 [1, 2, 3], ClientConfig {
2048 information_config: Default::default(),
2049 non_temporary_address_config: AddressConfig {
2050 address_count: 1,
2051 preferred_addresses: None,
2052 },
2053 prefix_delegation_config: None,
2054 },
2055 1, || Ok(client_socket),
2057 server_addr,
2058 client_stream,
2059 )
2060 .await
2061 .expect("failed to create test client");
2062
2063 let _: ReceivedMessage =
2065 assert_received_message(&server_socket, client_addr, v6::MessageType::Solicit).await;
2066 client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Retransmission]);
2067
2068 let mut buf = vec![0u8; MAX_UDP_DATAGRAM_SIZE];
2069 drop(client_proxy);
2071 assert_matches!(client.handle_next_event(&mut buf).await, Ok(None));
2072 }
2073
2074 #[fuchsia::test]
2075 #[should_panic = "received unexpected refresh timeout in state InformationRequesting"]
2076 async fn test_handle_next_event_respects_timer_order() {
2077 let (_client_end, client_stream) = create_request_stream::<ClientMarker>();
2078
2079 let (client_socket, client_addr) = create_test_socket();
2080 let (server_socket, server_addr) = create_test_socket();
2081 let mut client = Client::<fasync::net::UdpSocket>::start(
2082 None,
2083 [1, 2, 3], STATELESS_CLIENT_CONFIG,
2085 1, || Ok(client_socket),
2087 server_addr,
2088 client_stream,
2089 )
2090 .await
2091 .expect("failed to create test client");
2092
2093 let mut buf = vec![0u8; MAX_UDP_DATAGRAM_SIZE];
2094 client.schedule_timer(
2097 dhcpv6_core::client::ClientTimerType::Retransmission,
2098 MonotonicInstant::now() + Duration::from_secs(1_000_000),
2099 );
2100 client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Retransmission]);
2101
2102 send_msg_with_options(&server_socket, client_addr, [5, 6, 7], v6::MessageType::Reply, &[])
2105 .await
2106 .expect("failed to send test message");
2107 assert_matches!(client.handle_next_event(&mut buf).await, Ok(Some(())));
2110 client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Retransmission]);
2112
2113 client.schedule_timer(
2115 dhcpv6_core::client::ClientTimerType::Refresh,
2116 MonotonicInstant::now() + Duration::from_nanos(1),
2117 );
2118 client.assert_scheduled([
2120 dhcpv6_core::client::ClientTimerType::Retransmission,
2121 dhcpv6_core::client::ClientTimerType::Refresh,
2122 ]);
2123
2124 let unreachable = client.handle_next_event(&mut buf).await;
2128 panic!("{unreachable:?}");
2129 }
2130
2131 #[fuchsia::test]
2132 async fn test_handle_next_event_fails_on_recv_err() {
2133 struct StubSocket {}
2134 impl<'a> AsyncSocket<'a> for StubSocket {
2135 type RecvFromFut = futures::future::Ready<Result<(usize, SocketAddr), std::io::Error>>;
2136 type SendToFut = futures::future::Ready<Result<usize, std::io::Error>>;
2137
2138 fn recv_from(&'a self, _buf: &'a mut [u8]) -> Self::RecvFromFut {
2139 futures::future::ready(Err(std::io::Error::other("test recv error")))
2140 }
2141 fn send_to(&'a self, buf: &'a [u8], _addr: SocketAddr) -> Self::SendToFut {
2142 futures::future::ready(Ok(buf.len()))
2143 }
2144 }
2145
2146 let (_client_end, client_stream) = create_request_stream::<ClientMarker>();
2147
2148 let mut client = Client::<StubSocket>::start(
2149 None,
2150 [1, 2, 3], STATELESS_CLIENT_CONFIG,
2152 1, || Ok(StubSocket {}),
2154 std_socket_addr!("[::1]:0"),
2155 client_stream,
2156 )
2157 .await
2158 .expect("failed to create test client");
2159
2160 assert_matches!(
2161 client.handle_next_event(&mut [0u8]).await,
2162 Err(ClientError::SocketRecv(err)) if err.kind() == std::io::ErrorKind::Other
2163 );
2164 }
2165}