Skip to main content

dhcpv6_client/
client.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Implements a DHCPv6 client.
6use 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::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::rngs::StdRng;
41
42/// A thin wrapper around `zx::MonotonicInstant` that implements `dhcpv6_core::Instant`.
43#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
44pub(crate) struct MonotonicInstant(zx::MonotonicInstant);
45
46impl MonotonicInstant {
47    fn now() -> MonotonicInstant {
48        MonotonicInstant(zx::MonotonicInstant::get())
49    }
50}
51
52impl dhcpv6_core::Instant for MonotonicInstant {
53    fn duration_since(&self, MonotonicInstant(earlier): MonotonicInstant) -> Duration {
54        let Self(this) = *self;
55
56        let diff: zx::MonotonicDuration = this - earlier;
57
58        Duration::from_nanos(diff.into_nanos().try_into().unwrap_or_else(|e| {
59            panic!(
60                "failed to calculate duration since {:?} with instant {:?}: {}",
61                earlier, this, e,
62            )
63        }))
64    }
65
66    fn checked_add(&self, duration: Duration) -> Option<MonotonicInstant> {
67        Some(self.add(duration))
68    }
69}
70
71impl Add<Duration> for MonotonicInstant {
72    type Output = MonotonicInstant;
73
74    fn add(self, duration: Duration) -> MonotonicInstant {
75        let MonotonicInstant(this) = self;
76        MonotonicInstant(this + duration.into())
77    }
78}
79
80#[derive(Debug, thiserror::Error)]
81pub enum ClientError {
82    #[error("fidl error")]
83    Fidl(#[source] fidl::Error),
84    #[error("got watch request while the previous one is pending")]
85    DoubleWatch,
86    #[error("unsupported DHCPv6 configuration")]
87    UnsupportedConfigs,
88    #[error("socket create error")]
89    SocketCreate(std::io::Error),
90    #[error("socket receive error")]
91    SocketRecv(std::io::Error),
92    #[error("unimplemented DHCPv6 functionality: {:?}()", _0)]
93    Unimplemented(String),
94}
95
96/// Theoretical size limit for UDP datagrams.
97///
98/// NOTE: This does not take [jumbograms](https://tools.ietf.org/html/rfc2675) into account.
99const MAX_UDP_DATAGRAM_SIZE: usize = 65_535;
100
101#[pin_project::pin_project]
102struct Timers {
103    #[pin]
104    retransmission: fasync::Timer,
105    #[pin]
106    refresh: fasync::Timer,
107    #[pin]
108    renew: fasync::Timer,
109    #[pin]
110    rebind: fasync::Timer,
111    #[pin]
112    restart_server_discovery: fasync::Timer,
113
114    #[cfg(test)]
115    scheduled: HashSet<dhcpv6_core::client::ClientTimerType>,
116}
117
118impl Default for Timers {
119    fn default() -> Self {
120        let unscheduled = || fasync::Timer::new(fasync::MonotonicInstant::INFINITE);
121        Self {
122            retransmission: unscheduled(),
123            refresh: unscheduled(),
124            renew: unscheduled(),
125            rebind: unscheduled(),
126            restart_server_discovery: unscheduled(),
127            #[cfg(test)]
128            scheduled: Default::default(),
129        }
130    }
131}
132
133/// A DHCPv6 client.
134pub(crate) struct Client<S: for<'a> AsyncSocket<'a>> {
135    /// The interface the client is running on.
136    interface_id: fnet::InterfaceId,
137    /// Stores the hash of the last observed version of DNS servers by a watcher.
138    ///
139    /// The client uses this hash to determine whether new changes in DNS servers are observed and
140    /// updates should be replied to the watcher.
141    last_observed_dns_hash: u64,
142    /// Stores a responder to send DNS server updates.
143    dns_responder: Option<ClientWatchServersResponder>,
144    /// Stores a responder to send acquired addresses.
145    address_responder: Option<ClientWatchAddressResponder>,
146    /// Holds the discovered prefixes and their lifetimes.
147    prefixes: HashMap<fnet::Ipv6AddressWithPrefix, Lifetimes>,
148    /// Indicates whether or not the prefixes has changed since last yielded.
149    prefixes_changed: bool,
150    /// Stores a responder to send acquired prefixes.
151    prefixes_responder: Option<ClientWatchPrefixesResponder>,
152    /// Maintains the state for the client.
153    state_machine: dhcpv6_core::client::ClientStateMachine<MonotonicInstant, StdRng>,
154    /// The socket used to communicate with DHCPv6 servers.
155    socket: S,
156    /// The address to send outgoing messages to.
157    server_addr: SocketAddr,
158    /// All timers.
159    timers: Pin<Box<Timers>>,
160    /// A stream of FIDL requests to this client.
161    request_stream: ClientRequestStream,
162}
163
164/// A trait that allows stubbing [`fuchsia_async::net::UdpSocket`] in tests.
165pub(crate) trait AsyncSocket<'a> {
166    type RecvFromFut: Future<Output = Result<(usize, SocketAddr), std::io::Error>> + 'a;
167    type SendToFut: Future<Output = Result<usize, std::io::Error>> + 'a;
168
169    fn recv_from(&'a self, buf: &'a mut [u8]) -> Self::RecvFromFut;
170    fn send_to(&'a self, buf: &'a [u8], addr: SocketAddr) -> Self::SendToFut;
171}
172
173impl<'a> AsyncSocket<'a> for fasync::net::UdpSocket {
174    type RecvFromFut = fasync::net::UdpRecvFrom<'a>;
175    type SendToFut = fasync::net::SendTo<'a>;
176
177    fn recv_from(&'a self, buf: &'a mut [u8]) -> Self::RecvFromFut {
178        self.recv_from(buf)
179    }
180    fn send_to(&'a self, buf: &'a [u8], addr: SocketAddr) -> Self::SendToFut {
181        self.send_to(buf, addr)
182    }
183}
184
185/// Converts `InformationConfig` to a collection of `v6::OptionCode`.
186fn to_dhcpv6_option_codes(
187    InformationConfig { dns_servers }: InformationConfig,
188) -> Vec<v6::OptionCode> {
189    dns_servers.then_some(v6::OptionCode::DnsServers).into_iter().collect()
190}
191
192fn to_configured_addresses(
193    AddressConfig { address_count, preferred_addresses }: AddressConfig,
194) -> Result<HashMap<v6::IAID, HashSet<Ipv6Addr>>, ClientError> {
195    let preferred_addresses = preferred_addresses.unwrap_or(Vec::new());
196    if preferred_addresses.len() > address_count.into() {
197        return Err(ClientError::UnsupportedConfigs);
198    }
199
200    // TODO(https://fxbug.dev/42157844): make IAID consistent across
201    // configurations.
202    Ok((0..)
203        .map(v6::IAID::new)
204        .zip(
205            preferred_addresses
206                .into_iter()
207                .map(|fnet::Ipv6Address { addr, .. }| HashSet::from([Ipv6Addr::from(addr)]))
208                .chain(std::iter::repeat_with(HashSet::new)),
209        )
210        .take(address_count.into())
211        .collect())
212}
213
214// The client only supports a single IA_PD.
215//
216// TODO(https://fxbug.dev/42065403): Support multiple IA_PDs.
217const IA_PD_IAID: v6::IAID = v6::IAID::new(0);
218
219/// Creates a state machine for the input client config.
220fn create_state_machine(
221    duid: Option<dhcpv6_core::ClientDuid>,
222    ClientConfig {
223        information_config,
224        non_temporary_address_config,
225        prefix_delegation_config,
226    }: ClientConfig,
227) -> Result<
228    (
229        dhcpv6_core::client::ClientStateMachine<MonotonicInstant, StdRng>,
230        dhcpv6_core::client::Actions<MonotonicInstant>,
231    ),
232    ClientError,
233> {
234    let information_option_codes = to_dhcpv6_option_codes(information_config);
235    let configured_non_temporary_addresses = to_configured_addresses(non_temporary_address_config)?;
236    let configured_delegated_prefixes = prefix_delegation_config
237        .map(|prefix_delegation_config| {
238            let prefix = match prefix_delegation_config {
239                PrefixDelegationConfig::Empty(Empty {}) => Ok(None),
240                PrefixDelegationConfig::PrefixLength(prefix_len) => {
241                    if prefix_len == 0 {
242                        // Should have used `PrefixDelegationConfig::Empty`.
243                        return Err(ClientError::UnsupportedConfigs);
244                    }
245
246                    Subnet::new(Ipv6::UNSPECIFIED_ADDRESS, prefix_len).map(Some)
247                }
248                PrefixDelegationConfig::Prefix(fnet::Ipv6AddressWithPrefix {
249                    addr: fnet::Ipv6Address { addr, .. },
250                    prefix_len,
251                }) => {
252                    let addr = Ipv6Addr::from_bytes(addr);
253                    if addr == Ipv6::UNSPECIFIED_ADDRESS {
254                        // Should have used `PrefixDelegationConfig::PrefixLength`.
255                        return Err(ClientError::UnsupportedConfigs);
256                    }
257
258                    Subnet::new(addr, prefix_len).map(Some)
259                }
260            };
261
262            match prefix {
263                Ok(o) => Ok(HashMap::from([(IA_PD_IAID, HashSet::from_iter(o.into_iter()))])),
264                Err(SubnetError::PrefixTooLong | SubnetError::HostBitsSet) => {
265                    Err(ClientError::UnsupportedConfigs)
266                }
267            }
268        })
269        .transpose()?;
270
271    let now = MonotonicInstant::now();
272    match (
273        information_option_codes.is_empty(),
274        configured_non_temporary_addresses.is_empty(),
275        configured_delegated_prefixes,
276    ) {
277        (true, true, None) => Err(ClientError::UnsupportedConfigs),
278        (false, true, None) => {
279            if duid.is_some() {
280                Err(ClientError::UnsupportedConfigs)
281            } else {
282                Ok(dhcpv6_core::client::ClientStateMachine::start_stateless(
283                    information_option_codes,
284                    rand::make_rng(),
285                    now,
286                ))
287            }
288        }
289        (
290            _request_information,
291            _configure_non_temporary_addresses,
292            configured_delegated_prefixes,
293        ) => Ok(dhcpv6_core::client::ClientStateMachine::start_stateful(
294            if let Some(duid) = duid {
295                duid
296            } else {
297                return Err(ClientError::UnsupportedConfigs);
298            },
299            configured_non_temporary_addresses,
300            configured_delegated_prefixes.unwrap_or_else(Default::default),
301            information_option_codes,
302            rand::make_rng(),
303            now,
304        )),
305    }
306}
307
308/// Calculates a hash for the input.
309fn hash<H: Hash>(h: &H) -> u64 {
310    let mut dh = DefaultHasher::new();
311    h.hash(&mut dh);
312    dh.finish()
313}
314
315fn subnet_to_address_with_prefix(prefix: Subnet<Ipv6Addr>) -> fnet::Ipv6AddressWithPrefix {
316    fnet::Ipv6AddressWithPrefix {
317        addr: fnet::Ipv6Address { addr: prefix.network().ipv6_bytes() },
318        prefix_len: prefix.prefix(),
319    }
320}
321
322impl<S: for<'a> AsyncSocket<'a>> Client<S> {
323    /// Starts the client in `config`.
324    pub(crate) async fn start(
325        duid: Option<dhcpv6_core::ClientDuid>,
326        config: ClientConfig,
327        interface_id: fnet::InterfaceId,
328        socket_fn: impl FnOnce() -> std::io::Result<S>,
329        server_addr: SocketAddr,
330        request_stream: ClientRequestStream,
331    ) -> Result<Self, ClientError> {
332        let (state_machine, actions) = create_state_machine(duid, config)?;
333        let mut client = Self {
334            state_machine,
335            interface_id,
336            socket: socket_fn().map_err(ClientError::SocketCreate)?,
337            server_addr,
338            request_stream,
339            // Server watcher's API requires blocking iff the first call would return an empty list,
340            // so initialize this field with a hash of an empty list.
341            last_observed_dns_hash: hash(&Vec::<Ipv6Addr>::new()),
342            dns_responder: None,
343            address_responder: None,
344            prefixes: Default::default(),
345            prefixes_changed: false,
346            prefixes_responder: None,
347            timers: Box::pin(Default::default()),
348        };
349        client.run_actions(actions).await?;
350        Ok(client)
351    }
352
353    /// Runs a list of actions sequentially.
354    async fn run_actions(
355        &mut self,
356        actions: dhcpv6_core::client::Actions<MonotonicInstant>,
357    ) -> Result<(), ClientError> {
358        stream::iter(actions)
359            .map(Ok)
360            .try_fold(self, |client, action| async move {
361                match action {
362                    dhcpv6_core::client::Action::SendMessage(buf) => {
363                        match client.socket.send_to(&buf, client.server_addr).await {
364                            Ok(size) => assert_eq!(size, buf.len()),
365                            Err(e) => warn!(
366                                "failed to send message to {}: {}; will retransmit later",
367                                client.server_addr, e
368                            ),
369                        };
370                    }
371                    dhcpv6_core::client::Action::ScheduleTimer(timer_type, timeout) => {
372                        client.schedule_timer(timer_type, timeout)
373                    }
374                    dhcpv6_core::client::Action::CancelTimer(timer_type) => {
375                        client.cancel_timer(timer_type)
376                    }
377                    dhcpv6_core::client::Action::UpdateDnsServers(servers) => {
378                        client.maybe_send_dns_server_updates(servers)?;
379                    }
380                    dhcpv6_core::client::Action::IaNaUpdates(_) => {
381                        // TODO(https://fxbug.dev/42178828): add actions to
382                        // (re)schedule preferred and valid lifetime timers.
383                        // TODO(https://fxbug.dev/42178817): Add
384                        // action to remove the previous address.
385                        // TODO(https://fxbug.dev/42177252): Add action to add
386                        // the new address and cancel timers for old address.
387                    }
388                    dhcpv6_core::client::Action::IaPdUpdates(mut updates) => {
389                        let updates = {
390                            let ret =
391                                updates.remove(&IA_PD_IAID).expect("Update missing for IAID");
392                            debug_assert_eq!(updates, HashMap::new());
393                            ret
394                        };
395
396                        let Self { prefixes, prefixes_changed, .. } = client;
397
398                        let now = zx::MonotonicInstant::get();
399                        let nonzero_timevalue_to_zx_time = |tv| match tv {
400                            v6::NonZeroTimeValue::Finite(tv) => {
401                                now + zx::MonotonicDuration::from_seconds(tv.get().into())
402                            }
403                            v6::NonZeroTimeValue::Infinity => zx::MonotonicInstant::INFINITE,
404                        };
405
406                        let calculate_lifetimes = |dhcpv6_core::client::Lifetimes {
407                            preferred_lifetime,
408                            valid_lifetime,
409                        }| {
410                            Lifetimes {
411                                preferred_until: match preferred_lifetime {
412                                    v6::TimeValue::Zero => zx::MonotonicInstant::ZERO,
413                                    v6::TimeValue::NonZero(preferred_lifetime) => {
414                                        nonzero_timevalue_to_zx_time(preferred_lifetime)
415                                    },
416                                }.into_nanos(),
417                                valid_until: nonzero_timevalue_to_zx_time(valid_lifetime)
418                                    .into_nanos(),
419                            }
420                        };
421
422                        for (prefix, update) in updates.into_iter() {
423                            let fidl_prefix = subnet_to_address_with_prefix(prefix);
424
425                            match update {
426                                dhcpv6_core::client::IaValueUpdateKind::Added(lifetimes) => {
427                                    assert_matches!(
428                                        prefixes.insert(
429                                            fidl_prefix,
430                                            calculate_lifetimes(lifetimes)
431                                        ),
432                                        None,
433                                        "must not know about prefix {} to add it with lifetimes {:?}",
434                                        prefix, lifetimes,
435                                    );
436                                }
437                                dhcpv6_core::client::IaValueUpdateKind::UpdatedLifetimes(updated_lifetimes) => {
438                                    assert_matches!(
439                                        prefixes.get_mut(&fidl_prefix),
440                                        Some(lifetimes) => {
441                                            *lifetimes = calculate_lifetimes(updated_lifetimes);
442                                        },
443                                        "must know about prefix {} to update lifetimes with {:?}",
444                                        prefix, updated_lifetimes,
445                                    );
446                                }
447                                dhcpv6_core::client::IaValueUpdateKind::Removed => {
448                                    assert_matches!(
449                                        prefixes.remove(&fidl_prefix),
450                                        Some(_),
451                                        "must know about prefix {} to remove it",
452                                        prefix
453                                    );
454                                }
455                            }
456                        }
457
458                        // Mark the client has having updated prefixes so that
459                        // callers of `WatchPrefixes` receive the update.
460                        *prefixes_changed = true;
461                        client.maybe_send_prefixes()?;
462                    }
463                };
464                Ok(client)
465            })
466            .await
467            .map(|_: &mut Client<S>| ())
468    }
469
470    /// Sends the latest DNS servers if a watcher is watching, and the latest set of servers are
471    /// different from what the watcher has observed last time.
472    fn maybe_send_dns_server_updates(&mut self, servers: Vec<Ipv6Addr>) -> Result<(), ClientError> {
473        let servers_hash = hash(&servers);
474        if servers_hash == self.last_observed_dns_hash {
475            Ok(())
476        } else {
477            Ok(match self.dns_responder.take() {
478                Some(responder) => {
479                    self.send_dns_server_updates(responder, servers, servers_hash)?
480                }
481                None => (),
482            })
483        }
484    }
485
486    fn maybe_send_prefixes(&mut self) -> Result<(), ClientError> {
487        let Self { prefixes, prefixes_changed, prefixes_responder, .. } = self;
488
489        if !*prefixes_changed {
490            return Ok(());
491        }
492
493        let responder = if let Some(responder) = prefixes_responder.take() {
494            responder
495        } else {
496            return Ok(());
497        };
498
499        let prefixes = prefixes
500            .iter()
501            .map(|(prefix, lifetimes)| Prefix { prefix: *prefix, lifetimes: *lifetimes })
502            .collect::<Vec<_>>();
503
504        responder.send(&prefixes).map_err(ClientError::Fidl)?;
505        *prefixes_changed = false;
506        Ok(())
507    }
508
509    /// Sends a list of DNS servers to a watcher through the input responder and updates the last
510    /// observed hash.
511    fn send_dns_server_updates(
512        &mut self,
513        responder: ClientWatchServersResponder,
514        servers: Vec<Ipv6Addr>,
515        hash: u64,
516    ) -> Result<(), ClientError> {
517        let response: Vec<_> = servers
518            .iter()
519            .map(|addr| {
520                let address = fnet::Ipv6Address { addr: addr.ipv6_bytes() };
521                let zone_index = if addr.is_unicast_link_local() { self.interface_id } else { 0 };
522
523                fnet_name::DnsServer_ {
524                    address: Some(fnet::SocketAddress::Ipv6(fnet::Ipv6SocketAddress {
525                        address,
526                        zone_index,
527                        port: DEFAULT_DNS_PORT,
528                    })),
529                    source: Some(fnet_name::DnsServerSource::Dhcpv6(
530                        fnet_name::Dhcpv6DnsServerSource {
531                            source_interface: Some(self.interface_id),
532                            ..Default::default()
533                        },
534                    )),
535                    ..Default::default()
536                }
537            })
538            .collect();
539        responder
540            .send(&response)
541            // The channel will be closed on error, so return an error to stop the client.
542            .map_err(ClientError::Fidl)?;
543        self.last_observed_dns_hash = hash;
544        Ok(())
545    }
546
547    /// Schedules a timer for `timer_type` to fire at `instant`.
548    ///
549    /// If a timer for `timer_type` is already scheduled, the timer is
550    /// updated to fire at the new time.
551    fn schedule_timer(
552        &mut self,
553        timer_type: dhcpv6_core::client::ClientTimerType,
554        MonotonicInstant(instant): MonotonicInstant,
555    ) {
556        let timers = self.timers.as_mut().project();
557        let timer = match timer_type {
558            dhcpv6_core::client::ClientTimerType::Retransmission => timers.retransmission,
559            dhcpv6_core::client::ClientTimerType::Refresh => timers.refresh,
560            dhcpv6_core::client::ClientTimerType::Renew => timers.renew,
561            dhcpv6_core::client::ClientTimerType::Rebind => timers.rebind,
562            dhcpv6_core::client::ClientTimerType::RestartServerDiscovery => {
563                timers.restart_server_discovery
564            }
565        };
566        #[cfg(test)]
567        let _: bool = if instant == zx::MonotonicInstant::INFINITE {
568            timers.scheduled.remove(&timer_type)
569        } else {
570            timers.scheduled.insert(timer_type)
571        };
572        timer.reset(fasync::MonotonicInstant::from_zx(instant));
573    }
574
575    /// Cancels a previously scheduled timer for `timer_type`.
576    ///
577    /// If a timer was not previously scheduled for `timer_type`, this
578    /// call is effectively a no-op.
579    fn cancel_timer(&mut self, timer_type: dhcpv6_core::client::ClientTimerType) {
580        self.schedule_timer(timer_type, MonotonicInstant(zx::MonotonicInstant::INFINITE))
581    }
582
583    /// Handles a timeout.
584    async fn handle_timeout(
585        &mut self,
586        timer_type: dhcpv6_core::client::ClientTimerType,
587    ) -> Result<(), ClientError> {
588        // This timer just fired.
589        self.cancel_timer(timer_type);
590
591        let actions = self.state_machine.handle_timeout(timer_type, MonotonicInstant::now());
592        self.run_actions(actions).await
593    }
594
595    /// Handles a received message.
596    async fn handle_message_recv(&mut self, mut msg: &[u8]) -> Result<(), ClientError> {
597        let msg = match v6::Message::parse(&mut msg, ()) {
598            Ok(msg) => msg,
599            Err(e) => {
600                // Discard invalid messages.
601                //
602                // https://tools.ietf.org/html/rfc8415#section-16.
603                warn!("failed to parse received message: {}", e);
604                return Ok(());
605            }
606        };
607        let actions = self.state_machine.handle_message_receive(msg, MonotonicInstant::now());
608        self.run_actions(actions).await
609    }
610
611    /// Handles a FIDL request sent to this client.
612    fn handle_client_request(&mut self, request: ClientRequest) -> Result<(), ClientError> {
613        debug!("handling client request: {:?}", request);
614        match request {
615            ClientRequest::WatchServers { responder } => match self.dns_responder {
616                Some(_) => {
617                    // Drop the previous responder to close the channel.
618                    self.dns_responder = None;
619                    // Return an error to stop the client because the channel is closed.
620                    Err(ClientError::DoubleWatch)
621                }
622                None => {
623                    let dns_servers = self.state_machine.get_dns_servers();
624                    let servers_hash = hash(&dns_servers);
625                    if servers_hash != self.last_observed_dns_hash {
626                        // Something has changed from the last time, update the watcher.
627                        let () =
628                            self.send_dns_server_updates(responder, dns_servers, servers_hash)?;
629                    } else {
630                        // Nothing has changed, update the watcher later.
631                        self.dns_responder = Some(responder);
632                    }
633                    Ok(())
634                }
635            },
636            ClientRequest::WatchAddress { responder } => match self.address_responder.take() {
637                // The responder will be dropped and cause the channel to be closed.
638                Some(ClientWatchAddressResponder { .. }) => Err(ClientError::DoubleWatch),
639                None => {
640                    // TODO(https://fxbug.dev/42152192): Implement the address watcher.
641                    warn!("WatchAddress call will block forever as it is unimplemented");
642                    self.address_responder = Some(responder);
643                    Ok(())
644                }
645            },
646            ClientRequest::WatchPrefixes { responder } => match self.prefixes_responder.take() {
647                // The responder will be dropped and cause the channel to be closed.
648                Some(ClientWatchPrefixesResponder { .. }) => Err(ClientError::DoubleWatch),
649                None => {
650                    self.prefixes_responder = Some(responder);
651                    self.maybe_send_prefixes()
652                }
653            },
654            // TODO(https://fxbug.dev/42152193): Implement Shutdown.
655            ClientRequest::Shutdown { responder: _ } => {
656                Err(ClientError::Unimplemented("Shutdown".to_string()))
657            }
658        }
659    }
660
661    /// Handles the next event and returns the result.
662    ///
663    /// Takes a pre-allocated buffer to avoid repeated allocation.
664    ///
665    /// The returned `Option` is `None` if `request_stream` on the client is closed.
666    async fn handle_next_event(&mut self, buf: &mut [u8]) -> Result<Option<()>, ClientError> {
667        let timers = self.timers.as_mut().project();
668        let timer_type = select! {
669            () = timers.retransmission => {
670                dhcpv6_core::client::ClientTimerType::Retransmission
671            },
672            () = timers.refresh => {
673                dhcpv6_core::client::ClientTimerType::Refresh
674            },
675            () = timers.renew => {
676                dhcpv6_core::client::ClientTimerType::Renew
677            },
678            () = timers.rebind => {
679                dhcpv6_core::client::ClientTimerType::Rebind
680            },
681            () = timers.restart_server_discovery => {
682                dhcpv6_core::client::ClientTimerType::RestartServerDiscovery
683            },
684            recv_from_res = self.socket.recv_from(buf).fuse() => {
685                let (size, _addr) = recv_from_res.map_err(ClientError::SocketRecv)?;
686                self.handle_message_recv(&buf[..size]).await?;
687                return Ok(Some(()));
688            },
689            request = self.request_stream.try_next() => {
690                let request = request.map_err(ClientError::Fidl)?;
691                return request.map(|request| self.handle_client_request(request)).transpose();
692            }
693        };
694        self.handle_timeout(timer_type).await?;
695        Ok(Some(()))
696    }
697
698    #[cfg(test)]
699    fn assert_scheduled(
700        &self,
701        timers: impl IntoIterator<Item = dhcpv6_core::client::ClientTimerType>,
702    ) {
703        assert_eq!(self.timers.as_ref().scheduled, timers.into_iter().collect())
704    }
705}
706
707/// Creates a socket listening on the input address and binds it to the
708/// interface.
709fn create_socket(
710    addr: SocketAddr,
711    interface_id: fnet::InterfaceId,
712) -> std::io::Result<fasync::net::UdpSocket> {
713    let socket = socket2::Socket::new(
714        socket2::Domain::IPV6,
715        socket2::Type::DGRAM,
716        Some(socket2::Protocol::UDP),
717    )?;
718    // It is possible to run multiple clients on the same address.
719    socket.set_reuse_port(true)?;
720
721    // The client is created for a specific interface; bind to that interface.
722    let interface_id = u32::try_from(interface_id).map_err(|_| {
723        std::io::Error::new(std::io::ErrorKind::InvalidInput, "interface ID does not fit in u32")
724    })?;
725    let name = fuchsia_nix::net::if_::if_indextoname(interface_id)
726        .map_err(Into::<std::io::Error>::into)?;
727    socket.bind_device(Some(name.as_bytes()))?;
728
729    socket.bind(&addr.into())?;
730    fasync::net::UdpSocket::from_socket(socket.into())
731}
732
733fn duid_from_fidl(duid: Duid) -> Result<dhcpv6_core::ClientDuid, ()> {
734    /// According to [RFC 8415, section 11.2], DUID of type DUID-LLT has a type value of 1
735    ///
736    /// [RFC 8415, section 11.2]: https://datatracker.ietf.org/doc/html/rfc8415#section-11.2
737    const DUID_TYPE_LLT: [u8; 2] = [0, 1];
738    /// According to [RFC 8415, section 11.4], DUID of type DUID-LL has a type value of 3
739    ///
740    /// [RFC 8415, section 11.4]: https://datatracker.ietf.org/doc/html/rfc8415#section-11.4
741    const DUID_TYPE_LL: [u8; 2] = [0, 3];
742    /// According to [RFC 8415, section 11.5], DUID of type DUID-UUID has a type value of 4.
743    ///
744    /// [RFC 8415, section 11.5]: https://datatracker.ietf.org/doc/html/rfc8415#section-11.5
745    const DUID_TYPE_UUID: [u8; 2] = [0, 4];
746    /// According to [RFC 8415, section 11.2], the hardware type of Ethernet as assigned by
747    /// [IANA] is 1.
748    ///
749    /// [RFC 8415, section 11.2]: https://datatracker.ietf.org/doc/html/rfc8415#section-11.2
750    /// [IANA]: https://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml
751    const HARDWARE_TYPE_ETHERNET: [u8; 2] = [0, 1];
752    match duid {
753        // DUID-LLT with a MAC address is 14 bytes (2 bytes for the type + 2
754        // bytes for the hardware type + 4 bytes for the timestamp + 6 bytes
755        // for the MAC address), which is guaranteed to fit in the 18-byte limit
756        // of `ClientDuid`.
757        Duid::LinkLayerAddressPlusTime(LinkLayerAddressPlusTime {
758            time,
759            link_layer_address: LinkLayerAddress::Ethernet(mac),
760        }) => {
761            let mut duid = dhcpv6_core::ClientDuid::new();
762            duid.try_extend_from_slice(&DUID_TYPE_LLT).unwrap();
763            duid.try_extend_from_slice(&HARDWARE_TYPE_ETHERNET).unwrap();
764            duid.write_u32::<NetworkEndian>(time).unwrap();
765            duid.try_extend_from_slice(&mac.octets).unwrap();
766            Ok(duid)
767        }
768        // DUID-LL with a MAC address is 10 bytes (2 bytes for the type + 2
769        // bytes for the hardware type + 6 bytes for the MAC address), which
770        // is guaranteed to fit in the 18-byte limit of `ClientDuid`.
771        Duid::LinkLayerAddress(LinkLayerAddress::Ethernet(mac)) => Ok(DUID_TYPE_LL
772            .into_iter()
773            .chain(HARDWARE_TYPE_ETHERNET.into_iter())
774            .chain(mac.octets.into_iter())
775            .collect()),
776        // DUID-UUID is 18 bytes (2 bytes for the type + 16 bytes for the UUID),
777        // which is guaranteed to fit in the 18-byte limit of `ClientDuid`.
778        Duid::Uuid(uuid) => Ok(DUID_TYPE_UUID.into_iter().chain(uuid.into_iter()).collect()),
779        _ => Err(()),
780    }
781}
782
783/// Starts a client based on `params`.
784///
785/// `request` will be serviced by the client.
786pub(crate) async fn serve_client(
787    NewClientParams { interface_id, address, duid, config }: NewClientParams,
788    request: ServerEnd<ClientMarker>,
789) -> Result<()> {
790    let std_addr = Ipv6Addr::from(address.address.addr);
791    if std_addr.is_multicast()
792        || (std_addr.is_unicast_link_local() && address.zone_index != interface_id)
793    {
794        return request
795            .close_with_epitaph(zx::Status::INVALID_ARGS)
796            .context("closing request channel with epitaph");
797    }
798
799    let fnet_ext::SocketAddress(addr) = fnet::SocketAddress::Ipv6(address).into();
800    let servers_addr = IpAddr::from_str(RELAY_AGENT_AND_SERVER_LINK_LOCAL_MULTICAST_ADDRESS)
801        .with_context(|| {
802            format!(
803                "{} should be a valid IPv6 address",
804                RELAY_AGENT_AND_SERVER_LINK_LOCAL_MULTICAST_ADDRESS,
805            )
806        })?;
807    let duid = match duid.map(|fidl| duid_from_fidl(fidl)).transpose() {
808        Ok(duid) => duid,
809        Err(()) => {
810            return request
811                .close_with_epitaph(zx::Status::INVALID_ARGS)
812                .context("closing request channel with epitaph");
813        }
814    };
815    let (request_stream, control_handle) = request.into_stream_and_control_handle();
816    let mut client = match Client::<fasync::net::UdpSocket>::start(
817        duid,
818        config,
819        interface_id,
820        || create_socket(addr, interface_id),
821        SocketAddr::new(servers_addr, RELAY_AGENT_AND_SERVER_PORT),
822        request_stream,
823    )
824    .await
825    {
826        Ok(client) => client,
827        Err(ClientError::UnsupportedConfigs) => {
828            control_handle.shutdown_with_epitaph(zx::Status::INVALID_ARGS);
829            return Ok(());
830        }
831        Err(e) => {
832            return Err(e.into());
833        }
834    };
835    let mut buf = vec![0u8; MAX_UDP_DATAGRAM_SIZE];
836    loop {
837        match client.handle_next_event(&mut buf).await? {
838            Some(()) => (),
839            None => break Ok(()),
840        }
841    }
842}
843
844#[cfg(test)]
845mod tests {
846    use std::pin::pin;
847    use std::task::Poll;
848
849    use fidl::endpoints::{
850        ClientEnd, create_proxy, create_proxy_and_stream, create_request_stream,
851    };
852    use fidl_fuchsia_net_dhcpv6::{self as fnet_dhcpv6, ClientProxy, DEFAULT_CLIENT_PORT};
853    use fuchsia_async as fasync;
854    use futures::{TryFutureExt as _, join, poll};
855
856    use assert_matches::assert_matches;
857    use net_declare::{
858        fidl_ip_v6, fidl_ip_v6_with_prefix, fidl_mac, fidl_socket_addr, fidl_socket_addr_v6,
859        net_ip_v6, net_subnet_v6, std_socket_addr,
860    };
861    use net_types::ip::IpAddress as _;
862    use packet::serialize::InnerPacketBuilder;
863    use test_case::test_case;
864
865    use super::*;
866
867    /// Creates a test socket bound to an ephemeral port on localhost.
868    fn create_test_socket() -> (fasync::net::UdpSocket, SocketAddr) {
869        let addr: SocketAddr = std_socket_addr!("[::1]:0");
870        let socket = std::net::UdpSocket::bind(addr).expect("failed to create test socket");
871        let addr = socket.local_addr().expect("failed to get address of test socket");
872        (fasync::net::UdpSocket::from_socket(socket).expect("failed to create test socket"), addr)
873    }
874
875    struct ReceivedMessage {
876        transaction_id: [u8; 3],
877        // Client IDs are optional in Information Request messages.
878        //
879        // Per RFC 8415 section 18.2.6,
880        //
881        //   The client SHOULD include a Client Identifier option (see
882        //   Section 21.2) to identify itself to the server (however, see
883        //   Section 4.3.1 of [RFC7844] for reasons why a client may not want to
884        //   include this option).
885        //
886        // Per RFC 7844 section 4.3.1,
887        //
888        //   According to [RFC3315], a DHCPv6 client includes its client
889        //   identifier in most of the messages it sends. There is one exception,
890        //   however: the client is allowed to omit its client identifier when
891        //   sending Information-request messages.
892        client_id: Option<Vec<u8>>,
893    }
894
895    /// Asserts `socket` receives a message of `msg_type` from
896    /// `want_from_addr`.
897    async fn assert_received_message(
898        socket: &fasync::net::UdpSocket,
899        want_from_addr: SocketAddr,
900        msg_type: v6::MessageType,
901    ) -> ReceivedMessage {
902        let mut buf = vec![0u8; MAX_UDP_DATAGRAM_SIZE];
903        let (size, from_addr) =
904            socket.recv_from(&mut buf).await.expect("failed to receive on test server socket");
905        assert_eq!(from_addr, want_from_addr);
906        let buf = &mut &buf[..size]; // Implements BufferView.
907        let msg = v6::Message::parse(buf, ()).expect("failed to parse message");
908        assert_eq!(msg.msg_type(), msg_type);
909
910        let mut client_id = None;
911        for opt in msg.options() {
912            match opt {
913                v6::ParsedDhcpOption::ClientId(id) => {
914                    assert_eq!(core::mem::replace(&mut client_id, Some(id.to_vec())), None)
915                }
916                _ => {}
917            }
918        }
919
920        ReceivedMessage { transaction_id: *msg.transaction_id(), client_id: client_id }
921    }
922
923    const TEST_MAC: fnet::MacAddress = fidl_mac!("00:01:02:03:04:05");
924
925    #[test_case(
926        Duid::LinkLayerAddress(LinkLayerAddress::Ethernet(TEST_MAC)),
927        &[0, 3, 0, 1, 0, 1, 2, 3, 4, 5];
928        "ll"
929    )]
930    #[test_case(
931        Duid::LinkLayerAddressPlusTime(LinkLayerAddressPlusTime {
932            time: 0,
933            link_layer_address: LinkLayerAddress::Ethernet(TEST_MAC),
934        }),
935        &[0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5];
936        "llt"
937    )]
938    #[test_case(
939        Duid::Uuid([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
940        &[0, 4, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
941        "uuid"
942    )]
943    #[fuchsia::test]
944    fn test_duid_from_fidl(duid: Duid, want: &[u8]) {
945        assert_eq!(duid_from_fidl(duid), Ok(dhcpv6_core::ClientDuid::try_from(want).unwrap()));
946    }
947
948    #[fuchsia::test]
949    fn test_create_client_with_unsupported_config() {
950        let prefix_delegation_configs = [
951            None,
952            // Prefix length config without a non-zero length.
953            Some(PrefixDelegationConfig::PrefixLength(0)),
954            // Prefix length too long.
955            Some(PrefixDelegationConfig::PrefixLength(Ipv6Addr::BYTES * 8 + 1)),
956            // Network-bits unset.
957            Some(PrefixDelegationConfig::Prefix(fidl_ip_v6_with_prefix!("::/64"))),
958            // Host-bits set.
959            Some(PrefixDelegationConfig::Prefix(fidl_ip_v6_with_prefix!("a::1/64"))),
960        ];
961
962        for prefix_delegation_config in prefix_delegation_configs.iter() {
963            assert_matches!(
964                create_state_machine(
965                    prefix_delegation_config.is_some().then(|| CLIENT_ID.into()),
966                    ClientConfig {
967                        information_config: Default::default(),
968                        non_temporary_address_config: Default::default(),
969                        prefix_delegation_config: prefix_delegation_config.clone(),
970                    }
971                ),
972                Err(ClientError::UnsupportedConfigs),
973                "prefix_delegation_config={:?}",
974                prefix_delegation_config
975            );
976        }
977    }
978
979    const STATELESS_CLIENT_CONFIG: ClientConfig = ClientConfig {
980        information_config: InformationConfig { dns_servers: true },
981        non_temporary_address_config: AddressConfig { address_count: 0, preferred_addresses: None },
982        prefix_delegation_config: None,
983    };
984
985    #[fuchsia::test]
986    async fn test_client_stops_on_channel_close() {
987        let (client_proxy, server_end) = create_proxy::<ClientMarker>();
988
989        let ((), client_res) = join!(
990            async { drop(client_proxy) },
991            serve_client(
992                NewClientParams {
993                    interface_id: 1,
994                    address: fidl_socket_addr_v6!("[::1]:546"),
995                    config: STATELESS_CLIENT_CONFIG,
996                    duid: None,
997                },
998                server_end,
999            ),
1000        );
1001        client_res.expect("client future should return with Ok");
1002    }
1003
1004    fn client_proxy_watch_servers(
1005        client_proxy: &fnet_dhcpv6::ClientProxy,
1006    ) -> impl Future<Output = Result<(), fidl::Error>> {
1007        client_proxy.watch_servers().map_ok(|_: Vec<fidl_fuchsia_net_name::DnsServer_>| ())
1008    }
1009
1010    fn client_proxy_watch_address(
1011        client_proxy: &fnet_dhcpv6::ClientProxy,
1012    ) -> impl Future<Output = Result<(), fidl::Error>> {
1013        client_proxy.watch_address().map_ok(
1014            |_: (
1015                fnet::Subnet,
1016                fidl_fuchsia_net_interfaces_admin::AddressParameters,
1017                fidl::endpoints::ServerEnd<
1018                    fidl_fuchsia_net_interfaces_admin::AddressStateProviderMarker,
1019                >,
1020            )| (),
1021        )
1022    }
1023
1024    fn client_proxy_watch_prefixes(
1025        client_proxy: &fnet_dhcpv6::ClientProxy,
1026    ) -> impl Future<Output = Result<(), fidl::Error>> {
1027        client_proxy.watch_prefixes().map_ok(|_: Vec<fnet_dhcpv6::Prefix>| ())
1028    }
1029
1030    #[test_case(client_proxy_watch_servers; "watch_servers")]
1031    #[test_case(client_proxy_watch_address; "watch_address")]
1032    #[test_case(client_proxy_watch_prefixes; "watch_prefixes")]
1033    #[fuchsia::test]
1034    async fn test_client_should_return_error_on_double_watch<F>(watch: F)
1035    where
1036        F: AsyncFn(&fnet_dhcpv6::ClientProxy) -> Result<(), fidl::Error>,
1037    {
1038        let (client_proxy, server_end) = create_proxy::<ClientMarker>();
1039
1040        let (caller1_res, caller2_res, client_res) = join!(
1041            watch(&client_proxy),
1042            watch(&client_proxy),
1043            serve_client(
1044                NewClientParams {
1045                    interface_id: 1,
1046                    address: fidl_socket_addr_v6!("[::1]:546"),
1047                    config: STATELESS_CLIENT_CONFIG,
1048                    duid: None,
1049                },
1050                server_end,
1051            )
1052        );
1053
1054        assert_matches!(
1055            caller1_res,
1056            Err(fidl::Error::ClientChannelClosed { epitaph: fidl::Epitaph::PeerClosed, .. })
1057        );
1058        assert_matches!(
1059            caller2_res,
1060            Err(fidl::Error::ClientChannelClosed { epitaph: fidl::Epitaph::PeerClosed, .. })
1061        );
1062        assert!(
1063            client_res
1064                .expect_err("client should fail with double watch error")
1065                .to_string()
1066                .contains("got watch request while the previous one is pending")
1067        );
1068    }
1069
1070    const VALID_INFORMATION_CONFIGS: [InformationConfig; 2] =
1071        [InformationConfig { dns_servers: false }, InformationConfig { dns_servers: true }];
1072
1073    const VALID_DELEGATED_PREFIX_CONFIGS: [Option<PrefixDelegationConfig>; 4] = [
1074        Some(PrefixDelegationConfig::Empty(Empty {})),
1075        Some(PrefixDelegationConfig::PrefixLength(1)),
1076        Some(PrefixDelegationConfig::PrefixLength(127)),
1077        Some(PrefixDelegationConfig::Prefix(fidl_ip_v6_with_prefix!("a::/64"))),
1078    ];
1079
1080    // Can't be a const variable because we allocate a vector.
1081    fn get_valid_non_temporary_address_configs() -> [AddressConfig; 5] {
1082        [
1083            Default::default(),
1084            AddressConfig { address_count: 1, preferred_addresses: None },
1085            AddressConfig { address_count: 1, preferred_addresses: Some(Vec::new()) },
1086            AddressConfig {
1087                address_count: 1,
1088                preferred_addresses: Some(vec![fidl_ip_v6!("a::1")]),
1089            },
1090            AddressConfig {
1091                address_count: 2,
1092                preferred_addresses: Some(vec![fidl_ip_v6!("a::2")]),
1093            },
1094        ]
1095    }
1096
1097    #[fuchsia::test]
1098    fn test_client_starts_with_valid_args() {
1099        for information_config in VALID_INFORMATION_CONFIGS {
1100            for non_temporary_address_config in get_valid_non_temporary_address_configs() {
1101                for prefix_delegation_config in VALID_DELEGATED_PREFIX_CONFIGS {
1102                    let mut exec = fasync::TestExecutor::new();
1103
1104                    let (client_proxy, server_end) = create_proxy::<ClientMarker>();
1105
1106                    let test_fut = async {
1107                        join!(
1108                            client_proxy.watch_servers(),
1109                            serve_client(
1110                                NewClientParams {
1111                                    interface_id: 1,
1112                                    address: fidl_socket_addr_v6!("[::1]:546"),
1113                                    config: ClientConfig {
1114                                        information_config: information_config.clone(),
1115                                        non_temporary_address_config: non_temporary_address_config
1116                                            .clone(),
1117                                        prefix_delegation_config: prefix_delegation_config.clone(),
1118                                    },
1119                                    duid: (non_temporary_address_config.address_count != 0
1120                                        || prefix_delegation_config.is_some())
1121                                    .then(|| fnet_dhcpv6::Duid::LinkLayerAddress(
1122                                        fnet_dhcpv6::LinkLayerAddress::Ethernet(fidl_mac!(
1123                                            "00:11:22:33:44:55"
1124                                        ))
1125                                    )),
1126                                },
1127                                server_end
1128                            )
1129                        )
1130                    };
1131                    let mut test_fut = pin!(test_fut);
1132                    assert_matches!(
1133                        exec.run_until_stalled(&mut test_fut),
1134                        Poll::Pending,
1135                        "information_config={:?}, non_temporary_address_config={:?}, prefix_delegation_config={:?}",
1136                        information_config,
1137                        non_temporary_address_config,
1138                        prefix_delegation_config
1139                    );
1140                }
1141            }
1142        }
1143    }
1144
1145    const CLIENT_ID: [u8; 18] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17];
1146
1147    #[fuchsia::test]
1148    async fn test_client_starts_in_correct_mode() {
1149        for information_config @ InformationConfig { dns_servers } in VALID_INFORMATION_CONFIGS {
1150            for non_temporary_address_config @ AddressConfig {
1151                address_count,
1152                preferred_addresses: _,
1153            } in get_valid_non_temporary_address_configs()
1154            {
1155                for prefix_delegation_config in VALID_DELEGATED_PREFIX_CONFIGS {
1156                    let (stateful, want_msg_type) =
1157                        if address_count == 0 && prefix_delegation_config.is_none() {
1158                            if !dns_servers {
1159                                continue;
1160                            } else {
1161                                (false, v6::MessageType::InformationRequest)
1162                            }
1163                        } else {
1164                            (true, v6::MessageType::Solicit)
1165                        };
1166
1167                    let (_, client_stream): (ClientEnd<ClientMarker>, _) =
1168                        create_request_stream::<ClientMarker>();
1169
1170                    let (client_socket, client_addr) = create_test_socket();
1171                    let (server_socket, server_addr) = create_test_socket();
1172                    println!(
1173                        "{:?} {:?} {:?}",
1174                        information_config, non_temporary_address_config, prefix_delegation_config
1175                    );
1176                    let _: Client<fasync::net::UdpSocket> = Client::start(
1177                        stateful.then(|| CLIENT_ID.into()),
1178                        ClientConfig {
1179                            information_config: information_config.clone(),
1180                            non_temporary_address_config: non_temporary_address_config.clone(),
1181                            prefix_delegation_config: prefix_delegation_config.clone(),
1182                        },
1183                        1, /* interface ID */
1184                        || Ok(client_socket),
1185                        server_addr,
1186                        client_stream,
1187                    )
1188                    .await
1189                        .unwrap_or_else(|e| panic!(
1190                            "failed to create test client: {}; information_config={:?}, non_temporary_address_config={:?}, prefix_delegation_config={:?}",
1191                            e, information_config, non_temporary_address_config, prefix_delegation_config
1192                        ));
1193
1194                    let _: ReceivedMessage =
1195                        assert_received_message(&server_socket, client_addr, want_msg_type).await;
1196                }
1197            }
1198        }
1199    }
1200
1201    // TODO(https://fxbug.dev/335656784): Replace this with a netemul test that isn't
1202    // sensitive to implementation details.
1203    #[fuchsia::test]
1204    async fn test_client_fails_to_start_with_invalid_args() {
1205        for params in vec![
1206            // Interface ID and zone index mismatch on link-local address.
1207            NewClientParams {
1208                interface_id: 2,
1209                address: fnet::Ipv6SocketAddress {
1210                    address: fidl_ip_v6!("fe80::1"),
1211                    port: DEFAULT_CLIENT_PORT,
1212                    zone_index: 1,
1213                },
1214                config: STATELESS_CLIENT_CONFIG,
1215                duid: None,
1216            },
1217            // Multicast address is invalid.
1218            NewClientParams {
1219                interface_id: 1,
1220                address: fnet::Ipv6SocketAddress {
1221                    address: fidl_ip_v6!("ff01::1"),
1222                    port: DEFAULT_CLIENT_PORT,
1223                    zone_index: 1,
1224                },
1225                config: STATELESS_CLIENT_CONFIG,
1226                duid: None,
1227            },
1228            // Stateless with DUID.
1229            NewClientParams {
1230                interface_id: 1,
1231                address: fidl_socket_addr_v6!("[2001:db8::1]:12345"),
1232                config: STATELESS_CLIENT_CONFIG,
1233                duid: Some(fnet_dhcpv6::Duid::LinkLayerAddress(
1234                    fnet_dhcpv6::LinkLayerAddress::Ethernet(fidl_mac!("00:11:22:33:44:55")),
1235                )),
1236            },
1237            // Stateful missing DUID.
1238            NewClientParams {
1239                interface_id: 1,
1240                address: fidl_socket_addr_v6!("[2001:db8::1]:12345"),
1241                config: ClientConfig {
1242                    information_config: InformationConfig { dns_servers: true },
1243                    non_temporary_address_config: AddressConfig {
1244                        address_count: 1,
1245                        preferred_addresses: None,
1246                    },
1247                    prefix_delegation_config: None,
1248                },
1249                duid: None,
1250            },
1251        ] {
1252            let (client_proxy, server_end) = create_proxy::<ClientMarker>();
1253            let () =
1254                serve_client(params, server_end).await.expect("start server failed unexpectedly");
1255            // Calling any function on the client proxy should fail due to channel closed with
1256            // `INVALID_ARGS`.
1257            assert_matches!(
1258                client_proxy.watch_servers().await,
1259                Err(fidl::Error::ClientChannelClosed { epitaph, .. })
1260                    if epitaph == zx::Status::INVALID_ARGS
1261            );
1262        }
1263    }
1264
1265    fn create_test_dns_server(
1266        address: fnet::Ipv6Address,
1267        source_interface: u64,
1268        zone_index: u64,
1269    ) -> fnet_name::DnsServer_ {
1270        fnet_name::DnsServer_ {
1271            address: Some(fnet::SocketAddress::Ipv6(fnet::Ipv6SocketAddress {
1272                address,
1273                zone_index,
1274                port: DEFAULT_DNS_PORT,
1275            })),
1276            source: Some(fnet_name::DnsServerSource::Dhcpv6(fnet_name::Dhcpv6DnsServerSource {
1277                source_interface: Some(source_interface),
1278                ..Default::default()
1279            })),
1280            ..Default::default()
1281        }
1282    }
1283
1284    async fn send_msg_with_options(
1285        socket: &fasync::net::UdpSocket,
1286        to_addr: SocketAddr,
1287        transaction_id: [u8; 3],
1288        msg_type: v6::MessageType,
1289        options: &[v6::DhcpOption<'_>],
1290    ) -> Result<()> {
1291        let builder = v6::MessageBuilder::new(msg_type, transaction_id, options);
1292        let mut buf = vec![0u8; builder.bytes_len()];
1293        builder.serialize(&mut buf);
1294        let size = socket.send_to(&buf, to_addr).await?;
1295        assert_eq!(size, buf.len());
1296        Ok(())
1297    }
1298
1299    #[fuchsia::test]
1300    fn test_client_should_respond_to_dns_watch_requests() {
1301        let mut exec = fasync::TestExecutor::new();
1302
1303        let (client_proxy, client_stream) = create_proxy_and_stream::<ClientMarker>();
1304
1305        let (client_socket, client_addr) = create_test_socket();
1306        let (server_socket, server_addr) = create_test_socket();
1307        let mut client = exec
1308            .run_singlethreaded(Client::<fasync::net::UdpSocket>::start(
1309                None,
1310                STATELESS_CLIENT_CONFIG,
1311                1, /* interface ID */
1312                || Ok(client_socket),
1313                server_addr,
1314                client_stream,
1315            ))
1316            .expect("failed to create test client");
1317
1318        let ReceivedMessage { transaction_id: initial_transaction_id, client_id: _ } = exec
1319            .run_singlethreaded(assert_received_message(
1320                &server_socket,
1321                client_addr,
1322                v6::MessageType::InformationRequest,
1323            ));
1324
1325        type WatchServersResponseFut = <fnet_dhcpv6::ClientProxy as fnet_dhcpv6::ClientProxyInterface>::WatchServersResponseFut;
1326        type WatchServersResponse = <WatchServersResponseFut as Future>::Output;
1327
1328        struct Test<'a> {
1329            client: &'a mut Client<fasync::net::UdpSocket>,
1330            buf: Vec<u8>,
1331            watcher_fut: WatchServersResponseFut,
1332        }
1333
1334        impl<'a> Test<'a> {
1335            fn new(
1336                client: &'a mut Client<fasync::net::UdpSocket>,
1337                client_proxy: &ClientProxy,
1338            ) -> Self {
1339                Self {
1340                    client,
1341                    buf: vec![0u8; MAX_UDP_DATAGRAM_SIZE],
1342                    watcher_fut: client_proxy.watch_servers(),
1343                }
1344            }
1345
1346            async fn handle_next_event(&mut self) {
1347                self.client
1348                    .handle_next_event(&mut self.buf)
1349                    .await
1350                    .expect("test client failed to handle next event")
1351                    .expect("request stream closed");
1352            }
1353
1354            async fn refresh_client(
1355                &mut self,
1356                server_socket: &fasync::net::UdpSocket,
1357                client_addr: SocketAddr,
1358            ) -> [u8; 3] {
1359                // Make the client ready for another reply immediately on signal, so it can
1360                // start receiving updates without waiting for the full refresh timeout which is
1361                // unrealistic in tests.
1362                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                    let ReceivedMessage { transaction_id, client_id: _ } = assert_received_message(
1374                        server_socket,
1375                        client_addr,
1376                        v6::MessageType::InformationRequest,
1377                    )
1378                    .await;
1379                    transaction_id
1380                } else {
1381                    panic!("no refresh timer is scheduled and refresh is requested in test");
1382                }
1383            }
1384
1385            // Drive both the DHCPv6 client's event handling logic and the DNS server
1386            // watcher until the DNS server watcher receives an update from the client (or
1387            // the client unexpectedly exits).
1388            fn run(&mut self) -> impl Future<Output = WatchServersResponse> + use<'_, 'a> {
1389                let Self { client, buf, watcher_fut } = self;
1390                async move {
1391                    let client_fut = async {
1392                        loop {
1393                            client
1394                                .handle_next_event(buf)
1395                                .await
1396                                .expect("test client failed to handle next event")
1397                                .expect("request stream closed");
1398                        }
1399                    }
1400                    .fuse();
1401                    let mut client_fut = pin!(client_fut);
1402                    let mut watcher_fut = watcher_fut.fuse();
1403                    select! {
1404                        () = client_fut => panic!("test client returned unexpectedly"),
1405                        r = watcher_fut => r,
1406                    }
1407                }
1408            }
1409        }
1410
1411        {
1412            // No DNS configurations received yet.
1413            let mut test = Test::new(&mut client, &client_proxy);
1414
1415            // Handle the WatchServers request.
1416            exec.run_singlethreaded(test.handle_next_event());
1417            assert!(
1418                test.client.dns_responder.is_some(),
1419                "WatchServers responder should be present"
1420            );
1421
1422            // Send an empty list to the client, should not update watcher.
1423            exec.run_singlethreaded(send_msg_with_options(
1424                &server_socket,
1425                client_addr,
1426                initial_transaction_id,
1427                v6::MessageType::Reply,
1428                &[v6::DhcpOption::ServerId(&[1, 2, 3]), v6::DhcpOption::DnsServers(&[])],
1429            ))
1430            .expect("failed to send test reply");
1431            // Wait for the client to handle the next event (processing the reply we just
1432            // sent). Note that it is not enough to simply drive the client future until it
1433            // is stalled as we do elsewhere in the test, because we have no guarantee that
1434            // the netstack has delivered the UDP packet to the client by the time the
1435            // `send_to` call returned.
1436            exec.run_singlethreaded(test.handle_next_event());
1437            assert_matches!(exec.run_until_stalled(&mut pin!(test.run())), Poll::Pending);
1438
1439            // Send a list of DNS servers, the watcher should be updated accordingly.
1440            let transaction_id =
1441                exec.run_singlethreaded(test.refresh_client(&server_socket, client_addr));
1442            let dns_servers = [net_ip_v6!("fe80::1:2")];
1443            exec.run_singlethreaded(send_msg_with_options(
1444                &server_socket,
1445                client_addr,
1446                transaction_id,
1447                v6::MessageType::Reply,
1448                &[v6::DhcpOption::ServerId(&[1, 2, 3]), v6::DhcpOption::DnsServers(&dns_servers)],
1449            ))
1450            .expect("failed to send test reply");
1451            let want_servers = vec![create_test_dns_server(
1452                fidl_ip_v6!("fe80::1:2"),
1453                1, /* source interface */
1454                1, /* zone index */
1455            )];
1456            let servers = exec.run_singlethreaded(test.run()).expect("get servers");
1457            assert_eq!(servers, want_servers);
1458        } // drop `test_fut` so `client_fut` is no longer mutably borrowed.
1459
1460        {
1461            // No new changes, should not update watcher.
1462            let mut test = Test::new(&mut client, &client_proxy);
1463
1464            // Handle the WatchServers request.
1465            exec.run_singlethreaded(test.handle_next_event());
1466            assert!(
1467                test.client.dns_responder.is_some(),
1468                "WatchServers responder should be present"
1469            );
1470
1471            // Send the same list of DNS servers, should not update watcher.
1472            let transaction_id =
1473                exec.run_singlethreaded(test.refresh_client(&server_socket, client_addr));
1474            let dns_servers = [net_ip_v6!("fe80::1:2")];
1475            exec.run_singlethreaded(send_msg_with_options(
1476                &server_socket,
1477                client_addr,
1478                transaction_id,
1479                v6::MessageType::Reply,
1480                &[v6::DhcpOption::ServerId(&[1, 2, 3]), v6::DhcpOption::DnsServers(&dns_servers)],
1481            ))
1482            .expect("failed to send test reply");
1483            // Wait for the client to handle the next event (processing the reply we just
1484            // sent). Note that it is not enough to simply drive the client future until it
1485            // is stalled as we do elsewhere in the test, because we have no guarantee that
1486            // the netstack has delivered the UDP packet to the client by the time the
1487            // `send_to` call returned.
1488            exec.run_singlethreaded(test.handle_next_event());
1489            assert_matches!(exec.run_until_stalled(&mut pin!(test.run())), Poll::Pending);
1490
1491            // Send a different list of DNS servers, should update watcher.
1492            let transaction_id =
1493                exec.run_singlethreaded(test.refresh_client(&server_socket, client_addr));
1494            let dns_servers = [net_ip_v6!("fe80::1:2"), net_ip_v6!("1234::5:6")];
1495            exec.run_singlethreaded(send_msg_with_options(
1496                &server_socket,
1497                client_addr,
1498                transaction_id,
1499                v6::MessageType::Reply,
1500                &[v6::DhcpOption::ServerId(&[1, 2, 3]), v6::DhcpOption::DnsServers(&dns_servers)],
1501            ))
1502            .expect("failed to send test reply");
1503            let want_servers = vec![
1504                create_test_dns_server(
1505                    fidl_ip_v6!("fe80::1:2"),
1506                    1, /* source interface */
1507                    1, /* zone index */
1508                ),
1509                // Only set zone index for link local addresses.
1510                create_test_dns_server(
1511                    fidl_ip_v6!("1234::5:6"),
1512                    1, /* source interface */
1513                    0, /* zone index */
1514                ),
1515            ];
1516            let servers = exec.run_singlethreaded(test.run()).expect("get servers");
1517            assert_eq!(servers, want_servers);
1518        } // drop `test_fut` so `client_fut` is no longer mutably borrowed.
1519
1520        {
1521            // Send an empty list of DNS servers, should update watcher,
1522            // because this is different from what the watcher has seen
1523            // last time.
1524            let mut test = Test::new(&mut client, &client_proxy);
1525
1526            let transaction_id =
1527                exec.run_singlethreaded(test.refresh_client(&server_socket, client_addr));
1528            exec.run_singlethreaded(send_msg_with_options(
1529                &server_socket,
1530                client_addr,
1531                transaction_id,
1532                v6::MessageType::Reply,
1533                &[v6::DhcpOption::ServerId(&[1, 2, 3]), v6::DhcpOption::DnsServers(&[])],
1534            ))
1535            .expect("failed to send test reply");
1536            let want_servers = Vec::<fnet_name::DnsServer_>::new();
1537            assert_eq!(exec.run_singlethreaded(test.run()).expect("get servers"), want_servers);
1538        } // drop `test_fut` so `client_fut` is no longer mutably borrowed.
1539    }
1540
1541    #[fuchsia::test]
1542    async fn test_client_should_respond_with_dns_servers_on_first_watch_if_non_empty() {
1543        let (client_proxy, client_stream) = create_proxy_and_stream::<ClientMarker>();
1544
1545        let (client_socket, client_addr) = create_test_socket();
1546        let (server_socket, server_addr) = create_test_socket();
1547        let client = Client::<fasync::net::UdpSocket>::start(
1548            None,
1549            STATELESS_CLIENT_CONFIG,
1550            1, /* interface ID */
1551            || Ok(client_socket),
1552            server_addr,
1553            client_stream,
1554        )
1555        .await
1556        .expect("failed to create test client");
1557
1558        let ReceivedMessage { transaction_id: initial_txid, client_id: _ } =
1559            assert_received_message(
1560                &server_socket,
1561                client_addr,
1562                v6::MessageType::InformationRequest,
1563            )
1564            .await;
1565
1566        let dns_servers = [net_ip_v6!("fe80::1:2"), net_ip_v6!("1234::5:6")];
1567        send_msg_with_options(
1568            &server_socket,
1569            client_addr,
1570            initial_txid,
1571            v6::MessageType::Reply,
1572            &[v6::DhcpOption::ServerId(&[4, 5, 6]), v6::DhcpOption::DnsServers(&dns_servers)],
1573        )
1574        .await
1575        .expect("failed to send test message");
1576
1577        let buf = vec![0u8; MAX_UDP_DATAGRAM_SIZE];
1578        let handle_client_events_fut =
1579            futures::stream::try_unfold((client, buf), |(mut client, mut buf)| async {
1580                client
1581                    .handle_next_event(&mut buf)
1582                    .await
1583                    .map(|res| res.map(|()| ((), (client, buf))))
1584            })
1585            .try_fold((), |(), ()| futures::future::ready(Ok(())))
1586            .fuse();
1587        let mut handle_client_events_fut = pin!(handle_client_events_fut);
1588
1589        let want_servers = vec![
1590            create_test_dns_server(
1591                fidl_ip_v6!("fe80::1:2"),
1592                1, /* source interface */
1593                1, /* zone index */
1594            ),
1595            create_test_dns_server(
1596                fidl_ip_v6!("1234::5:6"),
1597                1, /* source interface */
1598                0, /* zone index */
1599            ),
1600        ];
1601        let found_servers = select!(
1602            status = handle_client_events_fut => panic!("client unexpectedly exited: {status:?}"),
1603            found_servers = client_proxy.watch_servers() => found_servers.expect(
1604                "watch servers should succeed"),
1605        );
1606        assert_eq!(found_servers, want_servers);
1607    }
1608
1609    #[fuchsia::test]
1610    async fn watch_prefixes() {
1611        const SERVER_ID: [u8; 3] = [3, 4, 5];
1612        const PREFERRED_LIFETIME_SECS: u32 = 1000;
1613        const VALID_LIFETIME_SECS: u32 = 2000;
1614        // Use the smallest possible value to enter the Renewing state
1615        // as fast as possible to keep the test's run-time as low as possible.
1616        const T1: u32 = 1;
1617        const T2: u32 = 2000;
1618
1619        let (client_proxy, client_stream) = create_proxy_and_stream::<ClientMarker>();
1620
1621        let (client_socket, client_addr) = create_test_socket();
1622        let (server_socket, server_addr) = create_test_socket();
1623        let mut client = Client::<fasync::net::UdpSocket>::start(
1624            Some(CLIENT_ID.into()),
1625            ClientConfig {
1626                information_config: Default::default(),
1627                non_temporary_address_config: Default::default(),
1628                prefix_delegation_config: Some(PrefixDelegationConfig::Empty(Empty {})),
1629            },
1630            1, /* interface ID */
1631            || Ok(client_socket),
1632            server_addr,
1633            client_stream,
1634        )
1635        .await
1636        .expect("failed to create test client");
1637
1638        let client_fut = async {
1639            let mut buf = vec![0u8; MAX_UDP_DATAGRAM_SIZE];
1640            loop {
1641                select! {
1642                    res = client.handle_next_event(&mut buf).fuse() => {
1643                        match res.expect("test client failed to handle next event") {
1644                            Some(()) => (),
1645                            None => break (),
1646                        };
1647                    }
1648                }
1649            }
1650        }
1651        .fuse();
1652        let mut client_fut = pin!(client_fut);
1653
1654        let update_prefix = net_subnet_v6!("a::/64");
1655        let remove_prefix = net_subnet_v6!("b::/64");
1656        let add_prefix = net_subnet_v6!("c::/64");
1657
1658        // Go through the motions to assign a prefix.
1659        let client_id = {
1660            let ReceivedMessage { client_id, transaction_id } =
1661                assert_received_message(&server_socket, client_addr, v6::MessageType::Solicit)
1662                    .await;
1663            // Client IDs are mandatory in stateful DHCPv6.
1664            let client_id = client_id.unwrap();
1665
1666            let ia_prefix = [
1667                v6::DhcpOption::IaPrefix(v6::IaPrefixSerializer::new(
1668                    PREFERRED_LIFETIME_SECS,
1669                    VALID_LIFETIME_SECS,
1670                    update_prefix,
1671                    &[],
1672                )),
1673                v6::DhcpOption::IaPrefix(v6::IaPrefixSerializer::new(
1674                    PREFERRED_LIFETIME_SECS,
1675                    VALID_LIFETIME_SECS,
1676                    remove_prefix,
1677                    &[],
1678                )),
1679            ];
1680            send_msg_with_options(
1681                &server_socket,
1682                client_addr,
1683                transaction_id,
1684                v6::MessageType::Advertise,
1685                &[
1686                    v6::DhcpOption::ServerId(&SERVER_ID),
1687                    v6::DhcpOption::ClientId(&client_id),
1688                    v6::DhcpOption::Preference(u8::MAX),
1689                    v6::DhcpOption::IaPd(v6::IaPdSerializer::new(IA_PD_IAID, T1, T2, &ia_prefix)),
1690                ],
1691            )
1692            .await
1693            .expect("failed to send adv message");
1694
1695            // Wait for the client to send a Request and send Reply so a prefix
1696            // is assigned.
1697            let transaction_id = select! {
1698                () = client_fut => panic!("should never return"),
1699                res = assert_received_message(
1700                    &server_socket,
1701                    client_addr,
1702                    v6::MessageType::Request,
1703                ).fuse() => {
1704                    let ReceivedMessage { client_id: req_client_id, transaction_id } = res;
1705                    assert_eq!(Some(&client_id), req_client_id.as_ref());
1706                    transaction_id
1707                },
1708            };
1709
1710            send_msg_with_options(
1711                &server_socket,
1712                client_addr,
1713                transaction_id,
1714                v6::MessageType::Reply,
1715                &[
1716                    v6::DhcpOption::ServerId(&SERVER_ID),
1717                    v6::DhcpOption::ClientId(&client_id),
1718                    v6::DhcpOption::IaPd(v6::IaPdSerializer::new(IA_PD_IAID, T1, T2, &ia_prefix)),
1719                ],
1720            )
1721            .await
1722            .expect("failed to send reply message");
1723
1724            client_id
1725        };
1726
1727        let check_watch_prefixes_result =
1728            |res: Result<Vec<Prefix>, _>,
1729             before_handling_reply,
1730             preferred_lifetime_secs: u32,
1731             valid_lifetime_secs: u32,
1732             expected_prefixes| {
1733                assert_matches!(
1734                    res.unwrap()[..],
1735                    [
1736                        Prefix {
1737                            prefix: got_prefix1,
1738                            lifetimes: Lifetimes {
1739                                preferred_until: preferred_until1,
1740                                valid_until: valid_until1,
1741                            },
1742                        },
1743                        Prefix {
1744                            prefix: got_prefix2,
1745                            lifetimes: Lifetimes {
1746                                preferred_until: preferred_until2,
1747                                valid_until: valid_until2,
1748                            },
1749                        },
1750                    ] => {
1751                        let now = zx::MonotonicInstant::get();
1752                        let preferred_until = zx::MonotonicInstant::from_nanos(preferred_until1);
1753                        let valid_until = zx::MonotonicInstant::from_nanos(valid_until1);
1754
1755                        let preferred_for = zx::MonotonicDuration::from_seconds(
1756                            preferred_lifetime_secs.into(),
1757                        );
1758                        let valid_for = zx::MonotonicDuration::from_seconds(valid_lifetime_secs.into());
1759
1760                        assert_eq!(
1761                            HashSet::from([got_prefix1, got_prefix2]),
1762                            HashSet::from(expected_prefixes),
1763                        );
1764                        assert!(preferred_until >= before_handling_reply + preferred_for);
1765                        assert!(preferred_until <= now + preferred_for);
1766                        assert!(valid_until >= before_handling_reply + valid_for);
1767                        assert!(valid_until <= now + valid_for);
1768
1769                        assert_eq!(preferred_until1, preferred_until2);
1770                        assert_eq!(valid_until1, valid_until2);
1771                    }
1772                )
1773            };
1774
1775        // Wait for a prefix to become assigned from the perspective of the DHCPv6
1776        // FIDL client.
1777        {
1778            // watch_prefixes should not return before a lease is negotiated. Note
1779            // that the client has not yet handled the Reply message.
1780            let mut watch_prefixes = client_proxy.watch_prefixes().fuse();
1781            assert_matches!(poll!(&mut watch_prefixes), Poll::Pending);
1782            let before_handling_reply = zx::MonotonicInstant::get();
1783            select! {
1784                () = client_fut => panic!("should never return"),
1785                res = watch_prefixes => check_watch_prefixes_result(
1786                    res,
1787                    before_handling_reply,
1788                    PREFERRED_LIFETIME_SECS,
1789                    VALID_LIFETIME_SECS,
1790                    [
1791                        subnet_to_address_with_prefix(update_prefix),
1792                        subnet_to_address_with_prefix(remove_prefix),
1793                    ],
1794                ),
1795            }
1796        }
1797
1798        // Wait for the client to attempt to renew the lease and go through the
1799        // motions to update the lease.
1800        {
1801            let transaction_id = select! {
1802                () = client_fut => panic!("should never return"),
1803                res = assert_received_message(
1804                    &server_socket,
1805                    client_addr,
1806                    v6::MessageType::Renew,
1807                ).fuse() => {
1808                    let ReceivedMessage { client_id: ren_client_id, transaction_id } = res;
1809                    assert_eq!(ren_client_id.as_ref(), Some(&client_id));
1810                    transaction_id
1811                },
1812            };
1813
1814            const NEW_PREFERRED_LIFETIME_SECS: u32 = 2 * PREFERRED_LIFETIME_SECS;
1815            const NEW_VALID_LIFETIME_SECS: u32 = 2 * VALID_LIFETIME_SECS;
1816            let ia_prefix = [
1817                v6::DhcpOption::IaPrefix(v6::IaPrefixSerializer::new(
1818                    NEW_PREFERRED_LIFETIME_SECS,
1819                    NEW_VALID_LIFETIME_SECS,
1820                    update_prefix,
1821                    &[],
1822                )),
1823                v6::DhcpOption::IaPrefix(v6::IaPrefixSerializer::new(
1824                    NEW_PREFERRED_LIFETIME_SECS,
1825                    NEW_VALID_LIFETIME_SECS,
1826                    add_prefix,
1827                    &[],
1828                )),
1829                v6::DhcpOption::IaPrefix(v6::IaPrefixSerializer::new(0, 0, remove_prefix, &[])),
1830            ];
1831
1832            send_msg_with_options(
1833                &server_socket,
1834                client_addr,
1835                transaction_id,
1836                v6::MessageType::Reply,
1837                &[
1838                    v6::DhcpOption::ServerId(&SERVER_ID),
1839                    v6::DhcpOption::ClientId(&client_id),
1840                    v6::DhcpOption::IaPd(v6::IaPdSerializer::new(
1841                        v6::IAID::new(0),
1842                        T1,
1843                        T2,
1844                        &ia_prefix,
1845                    )),
1846                ],
1847            )
1848            .await
1849            .expect("failed to send reply message");
1850
1851            let before_handling_reply = zx::MonotonicInstant::get();
1852            select! {
1853                () = client_fut => panic!("should never return"),
1854                res = client_proxy.watch_prefixes().fuse() => check_watch_prefixes_result(
1855                    res,
1856                    before_handling_reply,
1857                    NEW_PREFERRED_LIFETIME_SECS,
1858                    NEW_VALID_LIFETIME_SECS,
1859                    [
1860                        subnet_to_address_with_prefix(update_prefix),
1861                        subnet_to_address_with_prefix(add_prefix),
1862                    ],
1863                ),
1864            }
1865        }
1866    }
1867
1868    #[fuchsia::test]
1869    async fn test_client_schedule_and_cancel_timers() {
1870        let (_client_end, client_stream) = create_request_stream::<ClientMarker>();
1871
1872        let (client_socket, _client_addr) = create_test_socket();
1873        let (_server_socket, server_addr) = create_test_socket();
1874        let mut client = Client::<fasync::net::UdpSocket>::start(
1875            None,
1876            STATELESS_CLIENT_CONFIG,
1877            1, /* interface ID */
1878            || Ok(client_socket),
1879            server_addr,
1880            client_stream,
1881        )
1882        .await
1883        .expect("failed to create test client");
1884
1885        // Stateless DHCP client starts by scheduling a retransmission timer.
1886        client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Retransmission]);
1887
1888        client.cancel_timer(dhcpv6_core::client::ClientTimerType::Retransmission);
1889        client.assert_scheduled([]);
1890
1891        let now = MonotonicInstant::now();
1892        client.schedule_timer(
1893            dhcpv6_core::client::ClientTimerType::Refresh,
1894            now + Duration::from_nanos(1),
1895        );
1896        client.schedule_timer(
1897            dhcpv6_core::client::ClientTimerType::Retransmission,
1898            now + Duration::from_nanos(2),
1899        );
1900        client.assert_scheduled([
1901            dhcpv6_core::client::ClientTimerType::Retransmission,
1902            dhcpv6_core::client::ClientTimerType::Refresh,
1903        ]);
1904
1905        // We are allowed to reschedule a timer to fire at a new time.
1906        let now = MonotonicInstant::now();
1907        client.schedule_timer(
1908            dhcpv6_core::client::ClientTimerType::Refresh,
1909            now + Duration::from_nanos(1),
1910        );
1911        client.schedule_timer(
1912            dhcpv6_core::client::ClientTimerType::Retransmission,
1913            now + Duration::from_nanos(2),
1914        );
1915
1916        client.cancel_timer(dhcpv6_core::client::ClientTimerType::Refresh);
1917        client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Retransmission]);
1918
1919        // Ok to cancel a timer that is not scheduled.
1920        client.cancel_timer(dhcpv6_core::client::ClientTimerType::Refresh);
1921
1922        client.cancel_timer(dhcpv6_core::client::ClientTimerType::Retransmission);
1923        client.assert_scheduled([]);
1924
1925        // Ok to cancel a timer that is not scheduled.
1926        client.cancel_timer(dhcpv6_core::client::ClientTimerType::Retransmission);
1927    }
1928
1929    #[fuchsia::test]
1930    async fn test_handle_next_event_on_stateless_client() {
1931        let (client_proxy, client_stream) = create_proxy_and_stream::<ClientMarker>();
1932
1933        let (client_socket, client_addr) = create_test_socket();
1934        let (server_socket, server_addr) = create_test_socket();
1935        let mut client = Client::<fasync::net::UdpSocket>::start(
1936            None,
1937            STATELESS_CLIENT_CONFIG,
1938            1, /* interface ID */
1939            || Ok(client_socket),
1940            server_addr,
1941            client_stream,
1942        )
1943        .await
1944        .expect("failed to create test client");
1945
1946        // Starting the client in stateless should send an information request out.
1947        let ReceivedMessage { client_id, transaction_id: initial_txid } = assert_received_message(
1948            &server_socket,
1949            client_addr,
1950            v6::MessageType::InformationRequest,
1951        )
1952        .await;
1953        client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Retransmission]);
1954
1955        let mut buf = vec![0u8; MAX_UDP_DATAGRAM_SIZE];
1956        // Trigger a retransmission.
1957        assert_matches!(client.handle_next_event(&mut buf).await, Ok(Some(())));
1958        let ReceivedMessage { client_id: got_client_id, transaction_id: _ } =
1959            assert_received_message(
1960                &server_socket,
1961                client_addr,
1962                v6::MessageType::InformationRequest,
1963            )
1964            .await;
1965        assert_eq!(got_client_id, client_id);
1966        client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Retransmission]);
1967
1968        // Message targeting another transaction ID should be ignored.
1969        send_msg_with_options(&server_socket, client_addr, [5, 6, 7], v6::MessageType::Reply, &[])
1970            .await
1971            .expect("failed to send test message");
1972        assert_matches!(client.handle_next_event(&mut buf).await, Ok(Some(())));
1973        client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Retransmission]);
1974
1975        // Invalid messages should be discarded. Empty buffer is invalid.
1976        let size =
1977            server_socket.send_to(&[], client_addr).await.expect("failed to send test message");
1978        assert_eq!(size, 0);
1979        assert_matches!(client.handle_next_event(&mut buf).await, Ok(Some(())));
1980        client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Retransmission]);
1981
1982        // Message targeting this client should cause the client to transition state.
1983        send_msg_with_options(
1984            &server_socket,
1985            client_addr,
1986            initial_txid,
1987            v6::MessageType::Reply,
1988            &[v6::DhcpOption::ServerId(&[4, 5, 6])],
1989        )
1990        .await
1991        .expect("failed to send test message");
1992        assert_matches!(client.handle_next_event(&mut buf).await, Ok(Some(())));
1993        client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Refresh]);
1994
1995        // Reschedule a shorter timer for Refresh so we don't spend time waiting in test.
1996        client.schedule_timer(
1997            dhcpv6_core::client::ClientTimerType::Refresh,
1998            MonotonicInstant::now() + Duration::from_nanos(1),
1999        );
2000
2001        // Trigger a refresh.
2002        assert_matches!(client.handle_next_event(&mut buf).await, Ok(Some(())));
2003        let ReceivedMessage { client_id, transaction_id: _ } = assert_received_message(
2004            &server_socket,
2005            client_addr,
2006            v6::MessageType::InformationRequest,
2007        )
2008        .await;
2009        assert_eq!(got_client_id, client_id,);
2010        client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Retransmission]);
2011
2012        let test_fut = async {
2013            assert_matches!(client.handle_next_event(&mut buf).await, Ok(Some(())));
2014            client
2015                .dns_responder
2016                .take()
2017                .expect("test client did not get a channel responder")
2018                .send(&[fnet_name::DnsServer_ {
2019                    address: Some(fidl_socket_addr!("[fe01::2:3]:42")),
2020                    source: Some(fnet_name::DnsServerSource::Dhcpv6(
2021                        fnet_name::Dhcpv6DnsServerSource {
2022                            source_interface: Some(42),
2023                            ..Default::default()
2024                        },
2025                    )),
2026                    ..Default::default()
2027                }])
2028                .expect("failed to send response on test channel");
2029        };
2030        let (watcher_res, ()) = join!(client_proxy.watch_servers(), test_fut);
2031        let servers = watcher_res.expect("failed to watch servers");
2032        assert_eq!(
2033            servers,
2034            vec![fnet_name::DnsServer_ {
2035                address: Some(fidl_socket_addr!("[fe01::2:3]:42")),
2036                source: Some(fnet_name::DnsServerSource::Dhcpv6(
2037                    fnet_name::Dhcpv6DnsServerSource {
2038                        source_interface: Some(42),
2039                        ..Default::default()
2040                    },
2041                )),
2042                ..Default::default()
2043            }]
2044        );
2045
2046        // Drop the channel should cause `handle_next_event(&mut buf)` to return `None`.
2047        drop(client_proxy);
2048        assert_matches!(client.handle_next_event(&mut buf).await, Ok(None));
2049    }
2050
2051    #[fuchsia::test]
2052    async fn test_handle_next_event_on_stateful_client() {
2053        let (client_proxy, client_stream) = create_proxy_and_stream::<ClientMarker>();
2054
2055        let (client_socket, client_addr) = create_test_socket();
2056        let (server_socket, server_addr) = create_test_socket();
2057        let mut client = Client::<fasync::net::UdpSocket>::start(
2058            Some(CLIENT_ID.into()),
2059            ClientConfig {
2060                information_config: Default::default(),
2061                non_temporary_address_config: AddressConfig {
2062                    address_count: 1,
2063                    preferred_addresses: None,
2064                },
2065                prefix_delegation_config: None,
2066            },
2067            1, /* interface ID */
2068            || Ok(client_socket),
2069            server_addr,
2070            client_stream,
2071        )
2072        .await
2073        .expect("failed to create test client");
2074
2075        // Starting the client in stateful should send out a solicit.
2076        let _: ReceivedMessage =
2077            assert_received_message(&server_socket, client_addr, v6::MessageType::Solicit).await;
2078        client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Retransmission]);
2079
2080        let mut buf = vec![0u8; MAX_UDP_DATAGRAM_SIZE];
2081        // Drop the channel should cause `handle_next_event(&mut buf)` to return `None`.
2082        drop(client_proxy);
2083        assert_matches!(client.handle_next_event(&mut buf).await, Ok(None));
2084    }
2085
2086    #[fuchsia::test]
2087    #[should_panic = "received unexpected refresh timeout in state InformationRequesting"]
2088    async fn test_handle_next_event_respects_timer_order() {
2089        let (_client_end, client_stream) = create_request_stream::<ClientMarker>();
2090
2091        let (client_socket, client_addr) = create_test_socket();
2092        let (server_socket, server_addr) = create_test_socket();
2093        let mut client = Client::<fasync::net::UdpSocket>::start(
2094            None,
2095            STATELESS_CLIENT_CONFIG,
2096            1, /* interface ID */
2097            || Ok(client_socket),
2098            server_addr,
2099            client_stream,
2100        )
2101        .await
2102        .expect("failed to create test client");
2103
2104        let mut buf = vec![0u8; MAX_UDP_DATAGRAM_SIZE];
2105        // A retransmission timer is scheduled when starting the client in stateless mode. Cancel
2106        // it and create a new one with a longer timeout so the test is not flaky.
2107        client.schedule_timer(
2108            dhcpv6_core::client::ClientTimerType::Retransmission,
2109            MonotonicInstant::now() + Duration::from_secs(1_000_000),
2110        );
2111        client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Retransmission]);
2112
2113        // Trigger a message receive, the message is later discarded because transaction ID doesn't
2114        // match.
2115        send_msg_with_options(&server_socket, client_addr, [5, 6, 7], v6::MessageType::Reply, &[])
2116            .await
2117            .expect("failed to send test message");
2118        // There are now two pending events, the message receive is handled first because the timer
2119        // is far into the future.
2120        assert_matches!(client.handle_next_event(&mut buf).await, Ok(Some(())));
2121        // The retransmission timer is still here.
2122        client.assert_scheduled([dhcpv6_core::client::ClientTimerType::Retransmission]);
2123
2124        // Inserts a refresh timer that precedes the retransmission.
2125        client.schedule_timer(
2126            dhcpv6_core::client::ClientTimerType::Refresh,
2127            MonotonicInstant::now() + Duration::from_nanos(1),
2128        );
2129        // This timer is scheduled.
2130        client.assert_scheduled([
2131            dhcpv6_core::client::ClientTimerType::Retransmission,
2132            dhcpv6_core::client::ClientTimerType::Refresh,
2133        ]);
2134
2135        // Now handle_next_event(&mut buf) should trigger a refresh because it
2136        // precedes retransmission. Refresh is not expected while in
2137        // InformationRequesting state and should lead to a panic.
2138        let unreachable = client.handle_next_event(&mut buf).await;
2139        panic!("{unreachable:?}");
2140    }
2141
2142    #[fuchsia::test]
2143    async fn test_handle_next_event_fails_on_recv_err() {
2144        struct StubSocket {}
2145        impl<'a> AsyncSocket<'a> for StubSocket {
2146            type RecvFromFut = futures::future::Ready<Result<(usize, SocketAddr), std::io::Error>>;
2147            type SendToFut = futures::future::Ready<Result<usize, std::io::Error>>;
2148
2149            fn recv_from(&'a self, _buf: &'a mut [u8]) -> Self::RecvFromFut {
2150                futures::future::ready(Err(std::io::Error::other("test recv error")))
2151            }
2152            fn send_to(&'a self, buf: &'a [u8], _addr: SocketAddr) -> Self::SendToFut {
2153                futures::future::ready(Ok(buf.len()))
2154            }
2155        }
2156
2157        let (_client_end, client_stream) = create_request_stream::<ClientMarker>();
2158
2159        let mut client = Client::<StubSocket>::start(
2160            None,
2161            STATELESS_CLIENT_CONFIG,
2162            1, /* interface ID */
2163            || Ok(StubSocket {}),
2164            std_socket_addr!("[::1]:0"),
2165            client_stream,
2166        )
2167        .await
2168        .expect("failed to create test client");
2169
2170        assert_matches!(
2171            client.handle_next_event(&mut [0u8]).await,
2172            Err(ClientError::SocketRecv(err)) if err.kind() == std::io::ErrorKind::Other
2173        );
2174    }
2175}