Skip to main content

dhcpv4/
server.rs

1// Copyright 2018 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
5use crate::configuration::ServerParameters;
6use crate::protocol::identifier::ClientIdentifier;
7use crate::protocol::{DhcpOption, Message, MessageType, OpCode, OptionCode, ProtocolError};
8
9#[cfg(target_os = "fuchsia")]
10use crate::protocol::{FidlCompatible, FromFidlExt, IntoFidlExt};
11
12use anyhow::{Context as _, Error};
13use bstr::BString;
14
15#[cfg(target_os = "fuchsia")]
16use zx::Status;
17
18#[cfg(target_os = "fuchsia")]
19use log::{error, info};
20
21use log::warn;
22use net_types::ethernet::Mac as MacAddr;
23use net_types::ip::{Ipv4, PrefixLength};
24use serde::{Deserialize, Serialize};
25use std::collections::{BTreeSet, HashMap};
26use std::net::Ipv4Addr;
27use thiserror::Error;
28
29/// A minimal DHCP server.
30///
31/// This comment will be expanded upon in future CLs as the server design
32/// is iterated upon.
33pub struct Server<DS: DataStore, TS: SystemTimeSource = StdSystemTime> {
34    records: ClientRecords,
35    pool: AddressPool,
36    params: ServerParameters,
37    store: Option<DS>,
38    options_repo: HashMap<OptionCode, DhcpOption>,
39    time_source: TS,
40}
41
42// An interface for Server to retrieve the current time.
43pub trait SystemTimeSource {
44    fn with_current_time() -> Self;
45    fn now(&self) -> std::time::SystemTime;
46}
47
48// SystemTimeSource that uses std::time::SystemTime::now().
49pub struct StdSystemTime;
50
51impl SystemTimeSource for StdSystemTime {
52    fn with_current_time() -> Self {
53        StdSystemTime
54    }
55
56    fn now(&self) -> std::time::SystemTime {
57        std::time::SystemTime::now()
58    }
59}
60
61/// An interface for storing and loading DHCP server data.
62pub trait DataStore {
63    type Error: std::error::Error + std::marker::Send + std::marker::Sync + 'static;
64
65    /// Inserts the client record associated with the identifier.
66    fn insert(
67        &mut self,
68        client_id: &ClientIdentifier,
69        record: &LeaseRecord,
70    ) -> Result<(), Self::Error>;
71
72    /// Stores the DHCP option values served by the server.
73    fn store_options(&mut self, opts: &[DhcpOption]) -> Result<(), Self::Error>;
74
75    /// Stores the DHCP server's configuration parameters.
76    fn store_parameters(&mut self, params: &ServerParameters) -> Result<(), Self::Error>;
77
78    /// Deletes the client record associated with the identifier.
79    fn delete(&mut self, client_id: &ClientIdentifier) -> Result<(), Self::Error>;
80}
81
82/// The default string used by the Server to identify itself to the Stash service.
83pub const DEFAULT_STASH_ID: &str = "dhcpd";
84
85/// This enumerates the actions a DHCP server can take in response to a
86/// received client message. A `SendResponse(Message, Ipv4Addr)` indicates
87/// that a `Message` needs to be delivered back to the client.
88/// The server may optionally send a destination `Ipv4Addr` (if the protocol
89/// warrants it) to direct the response `Message` to.
90/// The other two variants indicate a successful processing of a client
91/// `Decline` or `Release`.
92/// Implements `PartialEq` for test assertions.
93#[derive(Debug, PartialEq)]
94pub enum ServerAction {
95    SendResponse(Message, ResponseTarget),
96    AddressDecline(Ipv4Addr),
97    AddressRelease(Ipv4Addr),
98}
99
100/// The destinations to which a response can be targeted. A `Broadcast`
101/// will be targeted to the IPv4 Broadcast address. A `Unicast` will be
102/// targeted to its `Ipv4Addr` associated value. If a `MacAddr` is supplied,
103/// the target may not yet have the `Ipv4Addr` assigned, so the response
104/// should be manually directed to the `MacAddr`, typically by updating the
105/// ARP cache.
106#[derive(Debug, PartialEq)]
107pub enum ResponseTarget {
108    Broadcast,
109    Unicast(Ipv4Addr, Option<MacAddr>),
110}
111
112/// A wrapper around the error types which can be returned by DHCP Server
113/// in response to client requests.
114/// Implements `PartialEq` for test assertions.
115#[derive(Debug, Error, PartialEq)]
116pub enum ServerError {
117    #[error("unexpected client message type: {}", _0)]
118    UnexpectedClientMessageType(MessageType),
119
120    #[error("requested ip parsing failure: {}", _0)]
121    BadRequestedIpv4Addr(String),
122
123    #[error("local address pool manipulation error: {}", _0)]
124    ServerAddressPoolFailure(AddressPoolError),
125
126    #[error("incorrect server ip in client message: {}", _0)]
127    IncorrectDHCPServer(Ipv4Addr),
128
129    #[error("requested ip mismatch with offered ip: {} {}", _0, _1)]
130    RequestedIpOfferIpMismatch(Ipv4Addr, Ipv4Addr),
131
132    #[error("expired client lease record")]
133    ExpiredLeaseRecord,
134
135    #[error("requested ip absent from server pool: {}", _0)]
136    UnidentifiedRequestedIp(Ipv4Addr),
137
138    #[error("unknown client identifier: {}", _0)]
139    UnknownClientId(ClientIdentifier),
140
141    #[error("init reboot request did not include ip")]
142    NoRequestedAddrAtInitReboot,
143
144    #[error("unidentified client state during request")]
145    UnknownClientStateDuringRequest,
146
147    #[error("decline request did not include ip")]
148    NoRequestedAddrForDecline,
149
150    #[error("client request error: {}", _0)]
151    ClientMessageError(ProtocolError),
152
153    #[error("error manipulating server data store: {}", _0)]
154    DataStoreUpdateFailure(DataStoreError),
155
156    #[error("server not configured with an ip address")]
157    ServerMissingIpAddr,
158
159    #[error("missing required dhcp option: {:?}", _0)]
160    MissingRequiredDhcpOption(OptionCode),
161
162    #[error("missing server identifier in response")]
163    // According to RFC 2131, page 28, all server responses MUST include server identifier.
164    //
165    // https://tools.ietf.org/html/rfc2131#page-29
166    MissingServerIdentifier,
167
168    #[error("unable to get system time")]
169    // The underlying error is not provided to this variant as it (std::time::SystemTimeError) does
170    // not implement PartialEq.
171    ServerTimeError,
172
173    #[error("inconsistent initial server state: {}", _0)]
174    InconsistentInitialServerState(AddressPoolError),
175
176    #[error("client request message missing requested ip addr")]
177    MissingRequestedAddr,
178
179    #[error("decline from unrecognized client: {:?}", _0)]
180    DeclineFromUnrecognizedClient(ClientIdentifier),
181
182    #[error(
183        "declined ip mismatched with lease: got declined addr {:?}, want client addr {:?}",
184        declined,
185        client
186    )]
187    DeclineIpMismatch { declined: Option<Ipv4Addr>, client: Option<Ipv4Addr> },
188
189    #[error("the client {client:?} does not currently hold a lease with {addr}")]
190    InvalidReleaseAddr { client: ClientIdentifier, addr: Ipv4Addr },
191}
192
193impl From<AddressPoolError> for ServerError {
194    fn from(e: AddressPoolError) -> Self {
195        ServerError::ServerAddressPoolFailure(e)
196    }
197}
198
199/// This struct is used to hold the error returned by the server's
200/// DataStore manipulation methods. We manually implement `PartialEq` so this
201/// struct could be included in the `ServerError` enum,
202/// which are asserted for equality in tests.
203#[derive(Debug, Error)]
204#[error(transparent)]
205pub struct DataStoreError(#[from] anyhow::Error);
206
207impl PartialEq for DataStoreError {
208    fn eq(&self, _other: &Self) -> bool {
209        false
210    }
211}
212
213impl<DS: DataStore, TS: SystemTimeSource> Server<DS, TS> {
214    /// Attempts to instantiate a new `Server` value from the persisted state contained in the
215    /// provided parts. If the client leases and address pool contained in the provided parts are
216    /// inconsistent with one another, then instantiation will fail.
217    pub fn new_from_state(
218        store: DS,
219        params: ServerParameters,
220        options_repo: HashMap<OptionCode, DhcpOption>,
221        records: ClientRecords,
222    ) -> Result<Self, Error> {
223        Self::new_with_time_source(store, params, options_repo, records, TS::with_current_time())
224    }
225
226    pub fn new_with_time_source(
227        store: DS,
228        params: ServerParameters,
229        options_repo: HashMap<OptionCode, DhcpOption>,
230        records: ClientRecords,
231        time_source: TS,
232    ) -> Result<Self, Error> {
233        let mut pool = AddressPool::new(params.managed_addrs.pool_range());
234        for client_addr in records.iter().filter_map(|(_id, LeaseRecord { current, .. })| *current)
235        {
236            pool.allocate_addr(client_addr).map_err(ServerError::InconsistentInitialServerState)?;
237        }
238        let mut server =
239            Self { records, pool, params, store: Some(store), options_repo, time_source };
240        server.release_expired_leases()?;
241        Ok(server)
242    }
243
244    /// Instantiates a new `Server`, without persisted state, from the supplied parameters.
245    pub fn new(store: Option<DS>, params: ServerParameters) -> Self {
246        Self {
247            records: HashMap::new(),
248            pool: AddressPool::new(params.managed_addrs.pool_range()),
249            params,
250            store,
251            options_repo: HashMap::new(),
252            time_source: TS::with_current_time(),
253        }
254    }
255
256    /// Dispatches an incoming DHCP message to the appropriate handler for processing.
257    ///
258    /// If the incoming message is a valid client DHCP message, then the server will attempt to
259    /// take appropriate action to serve the client's request, update the internal server state,
260    /// and return the suitable response.
261    /// If the incoming message is invalid, or the server is unable to serve the request,
262    /// or the processing of the client's request resulted in an error, then `dispatch()`
263    /// will return the fitting `Err` indicating what went wrong.
264    pub fn dispatch(&mut self, msg: Message) -> Result<ServerAction, ServerError> {
265        match msg.get_dhcp_type().map_err(ServerError::ClientMessageError)? {
266            MessageType::DHCPDISCOVER => self.handle_discover(msg),
267            MessageType::DHCPOFFER => {
268                Err(ServerError::UnexpectedClientMessageType(MessageType::DHCPOFFER))
269            }
270            MessageType::DHCPREQUEST => self.handle_request(msg),
271            MessageType::DHCPDECLINE => self.handle_decline(msg),
272            MessageType::DHCPACK => {
273                Err(ServerError::UnexpectedClientMessageType(MessageType::DHCPACK))
274            }
275            MessageType::DHCPNAK => {
276                Err(ServerError::UnexpectedClientMessageType(MessageType::DHCPNAK))
277            }
278            MessageType::DHCPRELEASE => self.handle_release(msg),
279            MessageType::DHCPINFORM => self.handle_inform(msg),
280        }
281    }
282
283    /// This method calculates the destination address of the server response
284    /// based on the conditions specified in -
285    /// https://tools.ietf.org/html/rfc2131#section-4.1 Page 22, Paragraph 4.
286    fn get_destination(&mut self, client_msg: &Message, offered: Ipv4Addr) -> ResponseTarget {
287        if !client_msg.giaddr.is_unspecified() {
288            ResponseTarget::Unicast(client_msg.giaddr, None)
289        } else if !client_msg.ciaddr.is_unspecified() {
290            ResponseTarget::Unicast(client_msg.ciaddr, None)
291        } else if client_msg.bdcast_flag {
292            ResponseTarget::Broadcast
293        } else {
294            ResponseTarget::Unicast(offered, Some(client_msg.chaddr))
295        }
296    }
297
298    fn handle_discover(&mut self, disc: Message) -> Result<ServerAction, ServerError> {
299        validate_discover(&disc)?;
300        let client_id = ClientIdentifier::from(&disc);
301        let offered = self.get_offered(&disc)?;
302        let dest = self.get_destination(&disc, offered);
303        let offer = self.build_offer(disc, offered)?;
304        match self.store_client_record(offered, client_id, &offer.options) {
305            Ok(()) => Ok(ServerAction::SendResponse(offer, dest)),
306            Err(e) => Err(ServerError::DataStoreUpdateFailure(e.into())),
307        }
308    }
309
310    // Determine the address to offer to the client.
311    //
312    // This function follows the address offer algorithm in
313    // https://tools.ietf.org/html/rfc2131#section-4.3.1:
314    //
315    // If an address is available, the new address SHOULD be chosen as follows:
316    //
317    // o The client's current address as recorded in the client's current
318    // binding, ELSE
319    //
320    // o The client's previous address as recorded in the client's (now
321    // expired or released) binding, if that address is in the server's
322    // pool of available addresses and not already allocated, ELSE
323    //
324    // o The address requested in the 'Requested IP Address' option, if that
325    // address is valid and not already allocated, ELSE
326    //
327    // o A new address allocated from the server's pool of available
328    // addresses; the address is selected based on the subnet from which
329    // the message was received (if 'giaddr' is 0) or on the address of
330    // the relay agent that forwarded the message ('giaddr' when not 0).
331    fn get_offered(&mut self, client: &Message) -> Result<Ipv4Addr, ServerError> {
332        let id = ClientIdentifier::from(client);
333        if let Some(LeaseRecord { current, previous, .. }) = self.records.get(&id) {
334            if let Some(current) = current {
335                if !self.pool.addr_is_allocated(*current) {
336                    panic!("address {} from active lease is unallocated in address pool", current);
337                }
338                return Ok(*current);
339            }
340            if let Some(previous) = previous {
341                if self.pool.addr_is_available(*previous) {
342                    return Ok(*previous);
343                }
344            }
345        }
346        if let Some(requested_addr) = get_requested_ip_addr(&client) {
347            if self.pool.addr_is_available(requested_addr) {
348                return Ok(requested_addr);
349            }
350        }
351        // TODO(https://fxbug.dev/42095285): The ip should be handed out based on
352        // client subnet. Currently, the server blindly hands out the next
353        // available ip from its available ip pool, without any subnet analysis.
354        if let Some(addr) = self.pool.available().next() {
355            return Ok(addr);
356        }
357        self.release_expired_leases()?;
358        if let Some(addr) = self.pool.available().next() {
359            return Ok(addr);
360        }
361        Err(ServerError::ServerAddressPoolFailure(AddressPoolError::Ipv4AddrExhaustion))
362    }
363
364    fn store_client_record(
365        &mut self,
366        offered: Ipv4Addr,
367        client_id: ClientIdentifier,
368        client_opts: &[DhcpOption],
369    ) -> Result<(), Error> {
370        let lease_length_seconds = client_opts
371            .iter()
372            .find_map(|opt| match opt {
373                DhcpOption::IpAddressLeaseTime(v) => Some(*v),
374                _ => None,
375            })
376            .ok_or(ServerError::MissingRequiredDhcpOption(OptionCode::IpAddressLeaseTime))?;
377        let options = client_opts
378            .iter()
379            .filter(|opt| {
380                // DhcpMessageType is part of transaction semantics and should not be stored.
381                opt.code() != OptionCode::DhcpMessageType
382            })
383            .cloned()
384            .collect();
385        let record =
386            LeaseRecord::new(Some(offered), options, self.time_source.now(), lease_length_seconds)?;
387        let Self { records, pool, store, .. } = self;
388        let entry = records.entry(client_id);
389        let current = match &entry {
390            std::collections::hash_map::Entry::Occupied(occupied) => {
391                let LeaseRecord { current, .. } = occupied.get();
392                *current
393            }
394            std::collections::hash_map::Entry::Vacant(_vacant) => None,
395        };
396        let newly_allocated = match current {
397            Some(current) => {
398                // If there is a lease record with a currently leased address, and the offered address
399                // does not match that leased address, then the offered address was calculated contrary
400                // to RFC2131#section4.3.1.
401                assert_eq!(
402                    current, offered,
403                    "server offered address does not match address in lease record"
404                );
405                false
406            }
407            None => {
408                match pool.allocate_addr(offered) {
409                    Ok(()) => (),
410                    // An error here indicates that the offered address is already allocated, an
411                    // irrecoverable inconsistency.
412                    Err(e) => panic!("fatal server address allocation failure: {}", e),
413                }
414                true
415            }
416        };
417        if let Some(store) = store {
418            if let Err(e) =
419                store.insert(entry.key(), &record).context("failed to store client in stash")
420            {
421                // The lease record is not inserted into `records` on this path, so expired lease
422                // reclamation (which only considers `records`) would never release the address.
423                // Undo the allocation performed above to avoid leaking the address, which would
424                // otherwise allow a client to exhaust the pool by repeatedly triggering data store
425                // failures.
426                if newly_allocated {
427                    match pool.release_addr(offered) {
428                        Ok(()) => (),
429                        Err(_) => unreachable!("address was just allocated above"),
430                    }
431                }
432                return Err(e);
433            }
434        }
435        let _ = entry.insert_entry(record);
436        Ok(())
437    }
438
439    fn handle_request(&mut self, req: Message) -> Result<ServerAction, ServerError> {
440        match get_client_state(&req).map_err(|()| ServerError::UnknownClientStateDuringRequest)? {
441            ClientState::Selecting => self.handle_request_selecting(req),
442            ClientState::InitReboot => self.handle_request_init_reboot(req),
443            ClientState::Renewing => self.handle_request_renewing(req),
444        }
445    }
446
447    fn handle_request_selecting(&mut self, req: Message) -> Result<ServerAction, ServerError> {
448        let requested_ip = get_requested_ip_addr(&req)
449            .ok_or(ServerError::MissingRequiredDhcpOption(OptionCode::RequestedIpAddress))?;
450        if !is_recipient(&self.params.server_ips, &req) {
451            Err(ServerError::IncorrectDHCPServer(
452                *self.params.server_ips.first().ok_or(ServerError::ServerMissingIpAddr)?,
453            ))
454        } else {
455            self.build_response(req, requested_ip)
456        }
457    }
458
459    fn build_response(
460        &mut self,
461        req: Message,
462        requested_ip: Ipv4Addr,
463    ) -> Result<ServerAction, ServerError> {
464        match self.validate_requested_addr_with_client(&req, requested_ip) {
465            Ok(()) => {
466                let dest = self.get_destination(&req, requested_ip);
467                Ok(ServerAction::SendResponse(self.build_ack(req, requested_ip)?, dest))
468            }
469            Err(e) => {
470                let (nak, dest) = self.build_nak(req, NakReason::ClientValidationFailure(e))?;
471                Ok(ServerAction::SendResponse(nak, dest))
472            }
473        }
474    }
475
476    /// The function below validates if the `requested_ip` is correctly
477    /// associated with the client whose request `req` is being processed.
478    ///
479    /// It first checks if the client bindings can be found in server records.
480    /// If not, the association is wrong and it returns an `Err()`.
481    ///
482    /// If the server can correctly locate the client bindings in its records,
483    /// it further verifies if the `requested_ip` is the same as the ip address
484    /// represented in the bindings and the binding is not expired and that the
485    /// `requested_ip` is no longer available in the server address pool. If
486    /// all the above conditions are met, it returns an `Ok(())` else the
487    /// appropriate `Err()` value is returned.
488    fn validate_requested_addr_with_client(
489        &self,
490        req: &Message,
491        requested_ip: Ipv4Addr,
492    ) -> Result<(), ServerError> {
493        let client_id = ClientIdentifier::from(req);
494        if let Some(record) = self.records.get(&client_id) {
495            let now = self
496                .time_source
497                .now()
498                .duration_since(std::time::UNIX_EPOCH)
499                .map_err(|std::time::SystemTimeError { .. }| ServerError::ServerTimeError)?;
500            if let Some(client_addr) = record.current {
501                if client_addr != requested_ip {
502                    Err(ServerError::RequestedIpOfferIpMismatch(requested_ip, client_addr))
503                } else if record.expired(now) {
504                    Err(ServerError::ExpiredLeaseRecord)
505                } else if !self.pool.addr_is_allocated(requested_ip) {
506                    Err(ServerError::UnidentifiedRequestedIp(requested_ip))
507                } else {
508                    Ok(())
509                }
510            } else {
511                Err(ServerError::MissingRequestedAddr)
512            }
513        } else {
514            Err(ServerError::UnknownClientId(client_id))
515        }
516    }
517
518    fn handle_request_init_reboot(&mut self, req: Message) -> Result<ServerAction, ServerError> {
519        let requested_ip =
520            get_requested_ip_addr(&req).ok_or(ServerError::NoRequestedAddrAtInitReboot)?;
521        if !is_in_subnet(&req, &self.params) {
522            let (nak, dest) = self.build_nak(req, NakReason::DifferentSubnets)?;
523            return Ok(ServerAction::SendResponse(nak, dest));
524        }
525        let client_id = ClientIdentifier::from(&req);
526        if !self.records.contains_key(&client_id) {
527            return Err(ServerError::UnknownClientId(client_id));
528        }
529        self.build_response(req, requested_ip)
530    }
531
532    fn handle_request_renewing(&mut self, req: Message) -> Result<ServerAction, ServerError> {
533        let client_ip = req.ciaddr;
534        self.build_response(req, client_ip)
535    }
536
537    // RFC 2131 provides limited guidance for implementation of DHCPDECLINE handling. From
538    // https://tools.ietf.org/html/rfc2131#section-4.3.3:
539    //
540    //   If the server receives a DHCPDECLINE message... The server MUST mark the network address
541    //   as not available...
542    //
543    // However, the RFC does not specify what a valid DHCPDECLINE message looks like. If all
544    // DHCPDECLINE messages are acted upon, then the server will be exposed to DoS attacks.
545    //
546    // We define a valid DHCPDECLINE message as:
547    //   * ServerIdentifier matches the server
548    //   * server has a record of a lease to the client
549    //   * the declined IP matches the leased IP
550    //
551    // Only if those three conditions obtain, the server will then invalidate the lease and mark
552    // the address as allocated and unavailable for assignment (if it isn't already).
553    fn handle_decline(&mut self, dec: Message) -> Result<ServerAction, ServerError> {
554        let Self { records, params, pool, store, .. } = self;
555        let declined_ip =
556            get_requested_ip_addr(&dec).ok_or_else(|| ServerError::NoRequestedAddrForDecline)?;
557        let id = ClientIdentifier::from(&dec);
558        if !is_recipient(&params.server_ips, &dec) {
559            return Err(ServerError::IncorrectDHCPServer(
560                get_server_id_from(&dec).ok_or(ServerError::MissingServerIdentifier)?,
561            ));
562        }
563        let entry = match records.entry(id) {
564            std::collections::hash_map::Entry::Occupied(v) => v,
565            std::collections::hash_map::Entry::Vacant(v) => {
566                return Err(ServerError::DeclineFromUnrecognizedClient(v.into_key()));
567            }
568        };
569        let LeaseRecord { current, .. } = entry.get();
570        if *current != Some(declined_ip) {
571            return Err(ServerError::DeclineIpMismatch {
572                declined: Some(declined_ip),
573                client: *current,
574            });
575        }
576        // The declined address must be marked allocated/unavailable. Depending on whether the
577        // client declines the address after an OFFER or an ACK, a declined address may already be
578        // marked allocated. Attempt to allocate the declined address, but treat the address
579        // already being allocated as success.
580        pool.allocate_addr(declined_ip).or_else(|e| match e {
581            AddressPoolError::AllocatedIpv4AddrAllocation(ip) if ip == declined_ip => Ok(()),
582            e @ AddressPoolError::Ipv4AddrExhaustion
583            | e @ AddressPoolError::AllocatedIpv4AddrAllocation(Ipv4Addr { .. })
584            | e @ AddressPoolError::UnallocatedIpv4AddrRelease(Ipv4Addr { .. })
585            | e @ AddressPoolError::UnmanagedIpv4Addr(Ipv4Addr { .. }) => Err(e),
586        })?;
587        let (id, LeaseRecord { .. }) = entry.remove_entry();
588        if let Some(store) = store {
589            store
590                .delete(&id)
591                .map_err(|e| ServerError::DataStoreUpdateFailure(anyhow::Error::from(e).into()))?;
592        }
593        Ok(ServerAction::AddressDecline(declined_ip))
594    }
595
596    fn handle_release(&mut self, rel: Message) -> Result<ServerAction, ServerError> {
597        let Self { records, pool, store, .. } = self;
598        let client_id = ClientIdentifier::from(&rel);
599        let client_ip = rel.ciaddr;
600        if let Some(record) = records.get_mut(&client_id) {
601            // From https://tools.ietf.org/html/rfc2131#section-4.3.4:
602            //
603            // Upon receipt of a DHCPRELEASE message, the server marks the network address as not
604            // allocated.  The server SHOULD retain a record of the client's initialization
605            // parameters for possible reuse in response to subsequent requests from the client.
606            //
607            // Note: Only release the address if the provided address matches our record.
608            if record.current.as_ref().is_some_and(|cur| cur == &client_ip) {
609                // Note: the following function panics if the record shows no current lease,
610                // the check above guarantees that the record has a current lease, so the
611                // function cannot panic.
612                release_leased_addr(&client_id, record, pool, store)?;
613                Ok(ServerAction::AddressRelease(client_ip))
614            } else {
615                Err(ServerError::InvalidReleaseAddr { client: client_id, addr: client_ip })
616            }
617        } else {
618            Err(ServerError::UnknownClientId(client_id))
619        }
620    }
621
622    fn handle_inform(&mut self, inf: Message) -> Result<ServerAction, ServerError> {
623        // When responding to an INFORM, the server must leave yiaddr zeroed.
624        let yiaddr = Ipv4Addr::UNSPECIFIED;
625        let dest = self.get_destination(&inf, inf.ciaddr);
626        let ack = self.build_inform_ack(inf, yiaddr)?;
627        Ok(ServerAction::SendResponse(ack, dest))
628    }
629
630    fn build_offer(&self, disc: Message, offered_ip: Ipv4Addr) -> Result<Message, ServerError> {
631        let server_ip = self.get_server_ip(&disc)?;
632        build_offer(
633            disc,
634            OfferOptions {
635                offered_ip,
636                server_ip,
637                lease_length_config: self.params.lease_length.clone(),
638                renewal_time_value: self.options_repo.get(&OptionCode::RenewalTimeValue).map(|v| {
639                    match v {
640                        DhcpOption::RenewalTimeValue(v) => *v,
641                        v => panic!(
642                            "options repo contains code-value mismatch: key={:?} value={:?}",
643                            OptionCode::RenewalTimeValue,
644                            v
645                        ),
646                    }
647                }),
648                rebinding_time_value: self.options_repo.get(&OptionCode::RebindingTimeValue).map(
649                    |v| match v {
650                        DhcpOption::RebindingTimeValue(v) => *v,
651                        v => panic!(
652                            "options repo contains code-value mismatch: key={:?} value={:?}",
653                            OptionCode::RenewalTimeValue,
654                            v
655                        ),
656                    },
657                ),
658                subnet_mask: self.params.managed_addrs.mask.into(),
659            },
660            &self.options_repo,
661        )
662    }
663
664    fn get_requested_options(&self, client_opts: &[DhcpOption]) -> Vec<DhcpOption> {
665        get_requested_options(
666            client_opts,
667            &self.options_repo,
668            self.params.managed_addrs.mask.into(),
669        )
670    }
671
672    fn build_ack(&self, req: Message, requested_ip: Ipv4Addr) -> Result<Message, ServerError> {
673        let client_id = ClientIdentifier::from(&req);
674        let options = match self.records.get(&client_id) {
675            Some(record) => {
676                let mut options = Vec::with_capacity(record.options.len() + 1);
677                options.push(DhcpOption::DhcpMessageType(MessageType::DHCPACK));
678                options.extend(record.options.iter().cloned());
679                options
680            }
681            None => return Err(ServerError::UnknownClientId(client_id)),
682        };
683        let ack = Message { op: OpCode::BOOTREPLY, secs: 0, yiaddr: requested_ip, options, ..req };
684        Ok(ack)
685    }
686
687    fn build_inform_ack(&self, inf: Message, client_ip: Ipv4Addr) -> Result<Message, ServerError> {
688        let server_ip = self.get_server_ip(&inf)?;
689        let mut options = Vec::new();
690        options.push(DhcpOption::DhcpMessageType(MessageType::DHCPACK));
691        options.push(DhcpOption::ServerIdentifier(server_ip));
692        options.extend_from_slice(&self.get_requested_options(&inf.options));
693        let ack = Message { op: OpCode::BOOTREPLY, secs: 0, yiaddr: client_ip, options, ..inf };
694        Ok(ack)
695    }
696
697    fn build_nak(
698        &self,
699        req: Message,
700        reason: NakReason,
701    ) -> Result<(Message, ResponseTarget), ServerError> {
702        let options = vec![
703            DhcpOption::DhcpMessageType(MessageType::DHCPNAK),
704            DhcpOption::ServerIdentifier(self.get_server_ip(&req)?),
705            DhcpOption::Message(format!("{}", reason)),
706        ];
707        let mut nak = Message {
708            op: OpCode::BOOTREPLY,
709            secs: 0,
710            ciaddr: Ipv4Addr::UNSPECIFIED,
711            yiaddr: Ipv4Addr::UNSPECIFIED,
712            siaddr: Ipv4Addr::UNSPECIFIED,
713            options,
714            ..req
715        };
716        // https://tools.ietf.org/html/rfc2131#section-4.3.2
717        // Page 31, Paragraph 2-3.
718        if nak.giaddr.is_unspecified() {
719            Ok((nak, ResponseTarget::Broadcast))
720        } else {
721            nak.bdcast_flag = true;
722            let giaddr = nak.giaddr;
723            Ok((nak, ResponseTarget::Unicast(giaddr, None)))
724        }
725    }
726
727    /// Determines the server identifier to use in DHCP responses. This
728    /// identifier is also the address the server should use to communicate with
729    /// the client.
730    ///
731    /// RFC 2131, Section 4.1, https://tools.ietf.org/html/rfc2131#section-4.1
732    ///
733    ///   The 'server identifier' field is used both to identify a DHCP server
734    ///   in a DHCP message and as a destination address from clients to
735    ///   servers.  A server with multiple network addresses MUST be prepared
736    ///   to to accept any of its network addresses as identifying that server
737    ///   in a DHCP message.  To accommodate potentially incomplete network
738    ///   connectivity, a server MUST choose an address as a 'server
739    ///   identifier' that, to the best of the server's knowledge, is reachable
740    ///   from the client.  For example, if the DHCP server and the DHCP client
741    ///   are connected to the same subnet (i.e., the 'giaddr' field in the
742    ///   message from the client is zero), the server SHOULD select the IP
743    ///   address the server is using for communication on that subnet as the
744    ///   'server identifier'.
745    fn get_server_ip(&self, req: &Message) -> Result<Ipv4Addr, ServerError> {
746        match get_server_id_from(&req) {
747            Some(addr) => {
748                if self.params.server_ips.contains(&addr) {
749                    Ok(addr)
750                } else {
751                    Err(ServerError::IncorrectDHCPServer(addr))
752                }
753            }
754            // TODO(https://fxbug.dev/42095285): This IP should be chosen based on the
755            // subnet of the client.
756            None => Ok(*self.params.server_ips.first().ok_or(ServerError::ServerMissingIpAddr)?),
757        }
758    }
759
760    /// Releases all allocated IP addresses whose leases have expired back to
761    /// the pool of addresses available for allocation.
762    fn release_expired_leases(&mut self) -> Result<(), ServerError> {
763        let Self { records, pool, time_source, store, .. } = self;
764        let now = time_source
765            .now()
766            .duration_since(std::time::UNIX_EPOCH)
767            .map_err(|std::time::SystemTimeError { .. }| ServerError::ServerTimeError)?;
768        records
769            .iter_mut()
770            .filter(|(_id, record)| record.current.is_some() && record.expired(now))
771            .try_for_each(|(id, record)| {
772                match release_leased_addr(id, record, pool, store) {
773                    Ok(()) => (),
774                    // Panic because server's state is irrecoverably inconsistent.
775                    Err(ServerError::ServerAddressPoolFailure(e)) => {
776                        panic!("fatal inconsistency in server address pool: {}", e)
777                    }
778                    Err(ServerError::DataStoreUpdateFailure(e)) => {
779                        warn!("failed to update data store: {}", e)
780                    }
781                    Err(e) => return Err(e),
782                };
783                Ok(())
784            })
785    }
786
787    #[cfg(target_os = "fuchsia")]
788    /// Saves current parameters to stash.
789    fn save_params(&mut self) -> Result<(), Status> {
790        if let Some(store) = self.store.as_mut() {
791            store.store_parameters(&self.params).map_err(|e| {
792                warn!("store_parameters({:?}) in stash failed: {}", self.params, e);
793                zx::Status::INTERNAL
794            })
795        } else {
796            Ok(())
797        }
798    }
799}
800
801/// Helper for constructing a repo of `DhcpOption`s.
802pub fn options_repo(
803    options: impl IntoIterator<Item = DhcpOption>,
804) -> HashMap<OptionCode, DhcpOption> {
805    options.into_iter().map(|option| (option.code(), option)).collect()
806}
807
808/// Parameters needed in order to build a DHCPOFFER.
809pub struct OfferOptions {
810    pub offered_ip: Ipv4Addr,
811    pub server_ip: Ipv4Addr,
812    pub lease_length_config: crate::configuration::LeaseLength,
813    pub renewal_time_value: Option<u32>,
814    pub rebinding_time_value: Option<u32>,
815    pub subnet_mask: PrefixLength<Ipv4>,
816}
817
818/// Builds a DHCPOFFER in response to the given DHCPDISCOVER using the provided
819/// `offer_options` and `options_repo`.
820pub fn build_offer(
821    disc: Message,
822    offer_options: OfferOptions,
823    options_repo: &HashMap<OptionCode, DhcpOption>,
824) -> Result<Message, ServerError> {
825    let OfferOptions {
826        offered_ip,
827        server_ip,
828        lease_length_config:
829            crate::configuration::LeaseLength {
830                default_seconds: default_lease_length_seconds,
831                max_seconds: max_lease_length_seconds,
832            },
833        renewal_time_value,
834        rebinding_time_value,
835        subnet_mask,
836    } = offer_options;
837    let mut options = Vec::new();
838    options.push(DhcpOption::DhcpMessageType(MessageType::DHCPOFFER));
839    options.push(DhcpOption::ServerIdentifier(server_ip));
840    let lease_length = match disc.options.iter().find_map(|opt| match opt {
841        DhcpOption::IpAddressLeaseTime(seconds) => Some(*seconds),
842        _ => None,
843    }) {
844        Some(seconds) => std::cmp::min(seconds, max_lease_length_seconds),
845        None => default_lease_length_seconds,
846    };
847    options.push(DhcpOption::IpAddressLeaseTime(lease_length));
848    let v = renewal_time_value.unwrap_or(lease_length / 2);
849    options.push(DhcpOption::RenewalTimeValue(v));
850    let v =
851        rebinding_time_value.unwrap_or_else(|| (lease_length / 4) * 3 + (lease_length % 4) * 3 / 4);
852    options.push(DhcpOption::RebindingTimeValue(v));
853    options.extend_from_slice(&get_requested_options(&disc.options, &options_repo, subnet_mask));
854    let offer = Message {
855        op: OpCode::BOOTREPLY,
856        secs: 0,
857        yiaddr: offered_ip,
858        ciaddr: Ipv4Addr::UNSPECIFIED,
859        siaddr: Ipv4Addr::UNSPECIFIED,
860        sname: BString::default(),
861        file: BString::default(),
862        options,
863        ..disc
864    };
865    Ok(offer)
866}
867
868/// Given the DHCP options set by the client, retrieves the values of the DHCP
869/// options requested by the client.
870pub fn get_requested_options(
871    client_opts: &[DhcpOption],
872    options_repo: &HashMap<OptionCode, DhcpOption>,
873    subnet_mask: PrefixLength<Ipv4>,
874) -> Vec<DhcpOption> {
875    // TODO(https://fxbug.dev/42056025): We should consider always supplying the
876    // SubnetMask for all DHCPDISCOVER and DHCPREQUEST requests. ISC
877    // does this, and we may desire to for increased compatibility with
878    // non-compliant clients.
879    //
880    // See: https://github.com/isc-projects/dhcp/commit/e9c5964
881
882    let prl = client_opts.iter().find_map(|opt| match opt {
883        DhcpOption::ParameterRequestList(v) => Some(v),
884        _ => None,
885    });
886    prl.map_or(Vec::new(), |requested_opts| {
887        let mut offered_opts: Vec<DhcpOption> = requested_opts
888            .iter()
889            .filter_map(|code| match options_repo.get(code) {
890                Some(opt) => Some(opt.clone()),
891                None => match code {
892                    OptionCode::SubnetMask => Some(DhcpOption::SubnetMask(subnet_mask)),
893                    _ => None,
894                },
895            })
896            .collect();
897
898        //  Enforce ordering SUBNET_MASK by moving it before ROUTER.
899        //  See: https://datatracker.ietf.org/doc/html/rfc2132#section-3.3
900        //
901        //      If both the subnet mask and the router option are specified
902        //      in a DHCP reply, the subnet mask option MUST be first.
903        let mut router_position = None;
904        for (i, option) in offered_opts.iter().enumerate() {
905            match option {
906                DhcpOption::Router(_) => router_position = Some(i),
907                DhcpOption::SubnetMask(_) => {
908                    if let Some(router_index) = router_position {
909                        offered_opts[router_index..(i + 1)].rotate_right(1)
910                    }
911                    // Once we find the subnet mask, we can bail on the for loop.
912                    break;
913                }
914                _ => continue,
915            }
916        }
917
918        offered_opts
919    })
920}
921
922// TODO(https://fxbug.dev/42154741): Find an alternative to panicking.
923fn release_leased_addr<DS: DataStore>(
924    id: &ClientIdentifier,
925    record: &mut LeaseRecord,
926    pool: &mut AddressPool,
927    store: &mut Option<DS>,
928) -> Result<(), ServerError> {
929    if let Some(addr) = record.current.take() {
930        record.previous = Some(addr);
931        pool.release_addr(addr)?;
932        if let Some(store) = store {
933            store
934                .insert(id, record)
935                .map_err(|e| ServerError::DataStoreUpdateFailure(anyhow::Error::from(e).into()))?;
936        }
937    } else {
938        panic!("attempted to release lease that has already been released: {:?}", record);
939    }
940    Ok(())
941}
942
943#[cfg(target_os = "fuchsia")]
944/// The ability to dispatch fuchsia.net.dhcp.Server protocol requests and return a value.
945///
946/// Implementers of this trait can be used as the backing server-side logic of the
947/// fuchsia.net.dhcp.Server protocol. Implementers must maintain a store of DHCP Options, DHCP
948/// server parameters, and leases issued to clients, and support the trait methods to retrieve and
949/// modify these stores.
950pub trait ServerDispatcher {
951    /// Validates the current set of server parameters returning a reference to
952    /// the parameters if the configuration is valid or an error otherwise.
953    fn try_validate_parameters(&self) -> Result<&ServerParameters, Status>;
954
955    /// Retrieves the stored DHCP option value that corresponds to the OptionCode argument.
956    fn dispatch_get_option(
957        &self,
958        code: fidl_fuchsia_net_dhcp::OptionCode,
959    ) -> Result<fidl_fuchsia_net_dhcp::Option_, Status>;
960    /// Retrieves the stored DHCP server parameter value that corresponds to the ParameterName argument.
961    fn dispatch_get_parameter(
962        &self,
963        name: fidl_fuchsia_net_dhcp::ParameterName,
964    ) -> Result<fidl_fuchsia_net_dhcp::Parameter, Status>;
965    /// Updates the stored DHCP option value to the argument.
966    fn dispatch_set_option(&mut self, value: fidl_fuchsia_net_dhcp::Option_) -> Result<(), Status>;
967    /// Updates the stored DHCP server parameter to the argument.
968    fn dispatch_set_parameter(
969        &mut self,
970        value: fidl_fuchsia_net_dhcp::Parameter,
971    ) -> Result<(), Status>;
972    /// Retrieves all of the stored DHCP option values.
973    fn dispatch_list_options(&self) -> Result<Vec<fidl_fuchsia_net_dhcp::Option_>, Status>;
974    /// Retrieves all of the stored DHCP parameter values.
975    fn dispatch_list_parameters(&self) -> Result<Vec<fidl_fuchsia_net_dhcp::Parameter>, Status>;
976    /// Resets all DHCP options to have no value.
977    fn dispatch_reset_options(&mut self) -> Result<(), Status>;
978    /// Resets all DHCP server parameters to their default values in `defaults`.
979    fn dispatch_reset_parameters(&mut self, defaults: &ServerParameters) -> Result<(), Status>;
980    /// Clears all leases from the store maintained by the ServerDispatcher.
981    fn dispatch_clear_leases(&mut self) -> Result<(), Status>;
982}
983
984#[cfg(target_os = "fuchsia")]
985impl<DS: DataStore, TS: SystemTimeSource> ServerDispatcher for Server<DS, TS> {
986    fn try_validate_parameters(&self) -> Result<&ServerParameters, Status> {
987        if !self.params.is_valid() {
988            return Err(Status::INVALID_ARGS);
989        }
990
991        // TODO(https://fxbug.dev/42140964): rethink this check and this function.
992        if self.pool.universe.is_empty() {
993            error!("Server validation failed: Address pool is empty");
994            return Err(Status::INVALID_ARGS);
995        }
996        Ok(&self.params)
997    }
998
999    fn dispatch_get_option(
1000        &self,
1001        code: fidl_fuchsia_net_dhcp::OptionCode,
1002    ) -> Result<fidl_fuchsia_net_dhcp::Option_, Status> {
1003        let opt_code =
1004            OptionCode::try_from(code as u8).map_err(|_protocol_error| Status::INVALID_ARGS)?;
1005        let option = self.options_repo.get(&opt_code).ok_or(Status::NOT_FOUND)?;
1006        let option = option.clone();
1007        let fidl_option = option.try_into_fidl().map_err(|protocol_error| {
1008            warn!(
1009                "server dispatcher could not convert dhcp option for fidl transport: {}",
1010                protocol_error
1011            );
1012            Status::INTERNAL
1013        })?;
1014        Ok(fidl_option)
1015    }
1016
1017    fn dispatch_get_parameter(
1018        &self,
1019        name: fidl_fuchsia_net_dhcp::ParameterName,
1020    ) -> Result<fidl_fuchsia_net_dhcp::Parameter, Status> {
1021        match name {
1022            fidl_fuchsia_net_dhcp::ParameterName::IpAddrs => {
1023                Ok(fidl_fuchsia_net_dhcp::Parameter::IpAddrs(
1024                    self.params.server_ips.clone().into_fidl(),
1025                ))
1026            }
1027            fidl_fuchsia_net_dhcp::ParameterName::AddressPool => {
1028                Ok(fidl_fuchsia_net_dhcp::Parameter::AddressPool(
1029                    self.params.managed_addrs.clone().into_fidl(),
1030                ))
1031            }
1032            fidl_fuchsia_net_dhcp::ParameterName::LeaseLength => {
1033                Ok(fidl_fuchsia_net_dhcp::Parameter::Lease(
1034                    self.params.lease_length.clone().into_fidl(),
1035                ))
1036            }
1037            fidl_fuchsia_net_dhcp::ParameterName::PermittedMacs => {
1038                Ok(fidl_fuchsia_net_dhcp::Parameter::PermittedMacs(
1039                    self.params.permitted_macs.clone().into_fidl(),
1040                ))
1041            }
1042            fidl_fuchsia_net_dhcp::ParameterName::StaticallyAssignedAddrs => {
1043                Ok(fidl_fuchsia_net_dhcp::Parameter::StaticallyAssignedAddrs(
1044                    self.params.static_assignments.clone().into_fidl(),
1045                ))
1046            }
1047            fidl_fuchsia_net_dhcp::ParameterName::ArpProbe => {
1048                Ok(fidl_fuchsia_net_dhcp::Parameter::ArpProbe(self.params.arp_probe))
1049            }
1050            fidl_fuchsia_net_dhcp::ParameterName::BoundDeviceNames => {
1051                Ok(fidl_fuchsia_net_dhcp::Parameter::BoundDeviceNames(
1052                    self.params.bound_device_names.clone(),
1053                ))
1054            }
1055        }
1056    }
1057
1058    fn dispatch_set_option(&mut self, value: fidl_fuchsia_net_dhcp::Option_) -> Result<(), Status> {
1059        let option = DhcpOption::try_from_fidl(value).map_err(|protocol_error| {
1060            warn!(
1061                "server dispatcher could not convert fidl argument into dhcp option: {}",
1062                protocol_error
1063            );
1064            Status::INVALID_ARGS
1065        })?;
1066        let _old = self.options_repo.insert(option.code(), option);
1067        let opts: Vec<DhcpOption> = self.options_repo.values().cloned().collect();
1068        if let Some(store) = self.store.as_mut() {
1069            store.store_options(&opts).map_err(|e| {
1070                warn!("store_options({:?}) in stash failed: {}", opts, e);
1071                zx::Status::INTERNAL
1072            })?;
1073        }
1074        Ok(())
1075    }
1076
1077    fn dispatch_set_parameter(
1078        &mut self,
1079        value: fidl_fuchsia_net_dhcp::Parameter,
1080    ) -> Result<(), Status> {
1081        match value {
1082            fidl_fuchsia_net_dhcp::Parameter::IpAddrs(ip_addrs) => {
1083                self.params.server_ips = Vec::<Ipv4Addr>::from_fidl(ip_addrs)
1084            }
1085            fidl_fuchsia_net_dhcp::Parameter::AddressPool(managed_addrs) => {
1086                // Be overzealous and do not allow the managed addresses to
1087                // change if we currently have leases.
1088                if !self.records.is_empty() {
1089                    return Err(Status::BAD_STATE);
1090                }
1091
1092                self.params.managed_addrs =
1093                    match crate::configuration::ManagedAddresses::try_from_fidl(managed_addrs) {
1094                        Ok(managed_addrs) => managed_addrs,
1095                        Err(e) => {
1096                            info!(
1097                                "dispatch_set_parameter() got invalid AddressPool argument: {:?}",
1098                                e
1099                            );
1100                            return Err(Status::INVALID_ARGS);
1101                        }
1102                    };
1103                // Update the pool with the new parameters.
1104                self.pool = AddressPool::new(self.params.managed_addrs.pool_range());
1105            }
1106            fidl_fuchsia_net_dhcp::Parameter::Lease(lease_length) => {
1107                self.params.lease_length =
1108                    match crate::configuration::LeaseLength::try_from_fidl(lease_length) {
1109                        Ok(lease_length) => lease_length,
1110                        Err(e) => {
1111                            info!(
1112                                "dispatch_set_parameter() got invalid LeaseLength argument: {}",
1113                                e
1114                            );
1115                            return Err(Status::INVALID_ARGS);
1116                        }
1117                    }
1118            }
1119            fidl_fuchsia_net_dhcp::Parameter::PermittedMacs(permitted_macs) => {
1120                self.params.permitted_macs =
1121                    crate::configuration::PermittedMacs::from_fidl(permitted_macs)
1122            }
1123            fidl_fuchsia_net_dhcp::Parameter::StaticallyAssignedAddrs(static_assignments) => {
1124                self.params.static_assignments =
1125                    match crate::configuration::StaticAssignments::try_from_fidl(static_assignments)
1126                    {
1127                        Ok(static_assignments) => static_assignments,
1128                        Err(e) => {
1129                            info!(
1130                                "dispatch_set_parameter() got invalid StaticallyAssignedAddrs argument: {}",
1131                                e
1132                            );
1133                            return Err(Status::INVALID_ARGS);
1134                        }
1135                    }
1136            }
1137            fidl_fuchsia_net_dhcp::Parameter::ArpProbe(arp_probe) => {
1138                self.params.arp_probe = arp_probe
1139            }
1140            fidl_fuchsia_net_dhcp::Parameter::BoundDeviceNames(bound_device_names) => {
1141                self.params.bound_device_names = bound_device_names
1142            }
1143            fidl_fuchsia_net_dhcp::ParameterUnknown!() => return Err(Status::INVALID_ARGS),
1144        };
1145        self.save_params()?;
1146        Ok(())
1147    }
1148
1149    fn dispatch_list_options(&self) -> Result<Vec<fidl_fuchsia_net_dhcp::Option_>, Status> {
1150        let options = self
1151            .options_repo
1152            .values()
1153            .filter_map(|option| {
1154                option
1155                    .clone()
1156                    .try_into_fidl()
1157                    .map_err(|protocol_error| {
1158                        warn!(
1159                        "server dispatcher could not convert dhcp option for fidl transport: {}",
1160                        protocol_error
1161                    );
1162                        Status::INTERNAL
1163                    })
1164                    .ok()
1165            })
1166            .collect::<Vec<fidl_fuchsia_net_dhcp::Option_>>();
1167        Ok(options)
1168    }
1169
1170    fn dispatch_list_parameters(&self) -> Result<Vec<fidl_fuchsia_net_dhcp::Parameter>, Status> {
1171        // Without this redundant borrow, the compiler will interpret this statement as a moving destructure.
1172        let ServerParameters {
1173            server_ips,
1174            managed_addrs,
1175            lease_length,
1176            permitted_macs,
1177            static_assignments,
1178            arp_probe,
1179            bound_device_names,
1180        } = &self.params;
1181        Ok(vec![
1182            fidl_fuchsia_net_dhcp::Parameter::IpAddrs(server_ips.clone().into_fidl()),
1183            fidl_fuchsia_net_dhcp::Parameter::AddressPool(managed_addrs.clone().into_fidl()),
1184            fidl_fuchsia_net_dhcp::Parameter::Lease(lease_length.clone().into_fidl()),
1185            fidl_fuchsia_net_dhcp::Parameter::PermittedMacs(permitted_macs.clone().into_fidl()),
1186            fidl_fuchsia_net_dhcp::Parameter::StaticallyAssignedAddrs(
1187                static_assignments.clone().into_fidl(),
1188            ),
1189            fidl_fuchsia_net_dhcp::Parameter::ArpProbe(*arp_probe),
1190            fidl_fuchsia_net_dhcp::Parameter::BoundDeviceNames(bound_device_names.clone()),
1191        ])
1192    }
1193
1194    fn dispatch_reset_options(&mut self) -> Result<(), Status> {
1195        self.options_repo.clear();
1196        let opts: Vec<DhcpOption> = self.options_repo.values().cloned().collect();
1197        if let Some(store) = self.store.as_mut() {
1198            store.store_options(&opts).map_err(|e| {
1199                warn!("store_options({:?}) in stash failed: {}", opts, e);
1200                zx::Status::INTERNAL
1201            })?;
1202        }
1203        Ok(())
1204    }
1205
1206    fn dispatch_reset_parameters(&mut self, defaults: &ServerParameters) -> Result<(), Status> {
1207        self.params = defaults.clone();
1208        self.save_params()?;
1209        Ok(())
1210    }
1211
1212    fn dispatch_clear_leases(&mut self) -> Result<(), Status> {
1213        let Self { records, pool, store, .. } = self;
1214        for (id, LeaseRecord { current, .. }) in records.drain() {
1215            if let Some(current) = current {
1216                match pool.release_addr(current) {
1217                    Ok(()) => (),
1218                    // Panic on failure because server has irrecoverable inconsistent state.
1219                    Err(e) => panic!("fatal server release address failure: {}", e),
1220                };
1221            }
1222            if let Some(store) = store {
1223                store.delete(&id).map_err(|e| {
1224                    warn!("delete({}) failed: {:?}", id, e);
1225                    zx::Status::INTERNAL
1226                })?;
1227            }
1228        }
1229        Ok(())
1230    }
1231}
1232
1233/// A mapping of clients to their lease records.
1234///
1235/// The server should store a record for each client to which it has sent
1236/// a DHCPOFFER message.
1237pub type ClientRecords = HashMap<ClientIdentifier, LeaseRecord>;
1238
1239/// A record of a DHCP client's configuration settings.
1240///
1241/// A client's `ClientIdentifier` maps to the `LeaseRecord`: this mapping
1242/// is stored in the `Server`s `ClientRecords` instance at runtime, and in
1243/// `fuchsia.stash` persistent storage.
1244#[derive(Clone, Debug, Deserialize, Serialize)]
1245pub struct LeaseRecord {
1246    current: Option<Ipv4Addr>,
1247    previous: Option<Ipv4Addr>,
1248    options: Vec<DhcpOption>,
1249    lease_start_epoch_seconds: u64,
1250    lease_length_seconds: u32,
1251}
1252
1253#[cfg(test)]
1254impl Default for LeaseRecord {
1255    fn default() -> Self {
1256        LeaseRecord {
1257            current: None,
1258            previous: None,
1259            options: Vec::new(),
1260            lease_start_epoch_seconds: u64::MIN,
1261            lease_length_seconds: u32::MAX,
1262        }
1263    }
1264}
1265
1266impl PartialEq for LeaseRecord {
1267    fn eq(&self, other: &Self) -> bool {
1268        // Only compare directly comparable fields.
1269        let LeaseRecord {
1270            current,
1271            previous,
1272            options,
1273            lease_start_epoch_seconds: _not_comparable,
1274            lease_length_seconds,
1275        } = self;
1276        let LeaseRecord {
1277            current: other_current,
1278            previous: other_previous,
1279            options: other_options,
1280            lease_start_epoch_seconds: _other_not_comparable,
1281            lease_length_seconds: other_lease_length_seconds,
1282        } = other;
1283        current == other_current
1284            && previous == other_previous
1285            && options == other_options
1286            && lease_length_seconds == other_lease_length_seconds
1287    }
1288}
1289
1290impl LeaseRecord {
1291    fn new(
1292        current: Option<Ipv4Addr>,
1293        options: Vec<DhcpOption>,
1294        lease_start: std::time::SystemTime,
1295        lease_length_seconds: u32,
1296    ) -> Result<Self, Error> {
1297        let lease_start_epoch_seconds =
1298            lease_start.duration_since(std::time::UNIX_EPOCH)?.as_secs();
1299        Ok(Self {
1300            current,
1301            previous: None,
1302            options,
1303            lease_start_epoch_seconds,
1304            lease_length_seconds,
1305        })
1306    }
1307
1308    fn expired(&self, since_unix_epoch: std::time::Duration) -> bool {
1309        let LeaseRecord { lease_start_epoch_seconds, lease_length_seconds, .. } = self;
1310        let end = std::time::Duration::from_secs(
1311            *lease_start_epoch_seconds + u64::from(*lease_length_seconds),
1312        );
1313        since_unix_epoch >= end
1314    }
1315}
1316
1317/// The pool of addresses managed by the server.
1318#[derive(Debug)]
1319struct AddressPool {
1320    // Morally immutable after construction, this is the full set of addresses
1321    // this pool manages, both allocated and available.
1322    //
1323    // TODO(https://fxbug.dev/42154213): make this type std::ops::Range.
1324    universe: BTreeSet<Ipv4Addr>,
1325    allocated: BTreeSet<Ipv4Addr>,
1326}
1327
1328//This is a wrapper around different errors that could be returned by
1329// the DHCP server address pool during address allocation/de-allocation.
1330#[derive(Debug, Error, PartialEq)]
1331pub enum AddressPoolError {
1332    #[error("address pool does not have any available ip to hand out")]
1333    Ipv4AddrExhaustion,
1334
1335    #[error("attempted to allocate already allocated ip: {}", _0)]
1336    AllocatedIpv4AddrAllocation(Ipv4Addr),
1337
1338    #[error("attempted to release unallocated ip: {}", _0)]
1339    UnallocatedIpv4AddrRelease(Ipv4Addr),
1340
1341    #[error("attempted to interact with out-of-pool ip: {}", _0)]
1342    UnmanagedIpv4Addr(Ipv4Addr),
1343}
1344
1345impl AddressPool {
1346    fn new<T: Iterator<Item = Ipv4Addr>>(addresses: T) -> Self {
1347        Self { universe: addresses.collect(), allocated: BTreeSet::new() }
1348    }
1349
1350    fn available(&self) -> impl Iterator<Item = Ipv4Addr> + '_ {
1351        let Self { universe: range, allocated } = self;
1352        range.difference(allocated).copied()
1353    }
1354
1355    fn allocate_addr(&mut self, addr: Ipv4Addr) -> Result<(), AddressPoolError> {
1356        if !self.universe.contains(&addr) {
1357            Err(AddressPoolError::UnmanagedIpv4Addr(addr))
1358        } else {
1359            if !self.allocated.insert(addr) {
1360                Err(AddressPoolError::AllocatedIpv4AddrAllocation(addr))
1361            } else {
1362                Ok(())
1363            }
1364        }
1365    }
1366
1367    fn release_addr(&mut self, addr: Ipv4Addr) -> Result<(), AddressPoolError> {
1368        if !self.universe.contains(&addr) {
1369            Err(AddressPoolError::UnmanagedIpv4Addr(addr))
1370        } else {
1371            if !self.allocated.remove(&addr) {
1372                Err(AddressPoolError::UnallocatedIpv4AddrRelease(addr))
1373            } else {
1374                Ok(())
1375            }
1376        }
1377    }
1378
1379    fn addr_is_available(&self, addr: Ipv4Addr) -> bool {
1380        self.universe.contains(&addr) && !self.allocated.contains(&addr)
1381    }
1382
1383    fn addr_is_allocated(&self, addr: Ipv4Addr) -> bool {
1384        self.allocated.contains(&addr)
1385    }
1386}
1387
1388#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1389enum ClientState {
1390    Selecting,
1391    InitReboot,
1392    Renewing,
1393}
1394
1395// Cf. RFC 2131 Table 5: https://tools.ietf.org/html/rfc2131#page-37
1396fn validate_discover(disc: &Message) -> Result<(), ServerError> {
1397    use std::string::ToString as _;
1398    if disc.op != OpCode::BOOTREQUEST {
1399        return Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
1400            field: String::from("op"),
1401            value: OpCode::BOOTREPLY.to_string(),
1402            msg_type: MessageType::DHCPDISCOVER,
1403        }));
1404    }
1405    if !disc.ciaddr.is_unspecified() {
1406        return Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
1407            field: String::from("ciaddr"),
1408            value: disc.ciaddr.to_string(),
1409            msg_type: MessageType::DHCPDISCOVER,
1410        }));
1411    }
1412    if !disc.yiaddr.is_unspecified() {
1413        return Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
1414            field: String::from("yiaddr"),
1415            value: disc.yiaddr.to_string(),
1416            msg_type: MessageType::DHCPDISCOVER,
1417        }));
1418    }
1419    if !disc.siaddr.is_unspecified() {
1420        return Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
1421            field: String::from("siaddr"),
1422            value: disc.siaddr.to_string(),
1423            msg_type: MessageType::DHCPDISCOVER,
1424        }));
1425    }
1426    // Do not check giaddr, because although a client will never set it, an
1427    // intervening relay agent may have done.
1428    if let Some(DhcpOption::ServerIdentifier(addr)) = disc.options.iter().find(|opt| match opt {
1429        DhcpOption::ServerIdentifier(_) => true,
1430        _ => false,
1431    }) {
1432        return Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
1433            field: String::from("ServerIdentifier"),
1434            value: addr.to_string(),
1435            msg_type: MessageType::DHCPDISCOVER,
1436        }));
1437    }
1438    Ok(())
1439}
1440
1441fn is_recipient(server_ips: &Vec<Ipv4Addr>, req: &Message) -> bool {
1442    if let Some(server_id) = get_server_id_from(&req) {
1443        server_ips.contains(&server_id)
1444    } else {
1445        false
1446    }
1447}
1448
1449fn is_in_subnet(req: &Message, config: &ServerParameters) -> bool {
1450    let client_ip = match get_requested_ip_addr(&req) {
1451        Some(ip) => ip,
1452        None => return false,
1453    };
1454    config.server_ips.iter().any(|server_ip| {
1455        config.managed_addrs.mask.apply_to(&client_ip)
1456            == config.managed_addrs.mask.apply_to(server_ip)
1457    })
1458}
1459
1460fn get_client_state(msg: &Message) -> Result<ClientState, ()> {
1461    let server_id = get_server_id_from(&msg);
1462    let requested_ip = get_requested_ip_addr(&msg);
1463
1464    // State classification from: https://tools.ietf.org/html/rfc2131#section-4.3.2
1465    //
1466    // DHCPREQUEST generated during SELECTING state:
1467    //
1468    // Client inserts the address of the selected server in 'server identifier', 'ciaddr' MUST be
1469    // zero, 'requested IP address' MUST be filled in with the yiaddr value from the chosen
1470    // DHCPOFFER.
1471    //
1472    // DHCPREQUEST generated during INIT-REBOOT state:
1473    //
1474    // 'server identifier' MUST NOT be filled in, 'requested IP address' option MUST be
1475    // filled in with client's notion of its previously assigned address. 'ciaddr' MUST be
1476    // zero.
1477    //
1478    // DHCPREQUEST generated during RENEWING state:
1479    //
1480    // 'server identifier' MUST NOT be filled in, 'requested IP address' option MUST NOT be filled
1481    // in, 'ciaddr' MUST be filled in with client's IP address.
1482    //
1483    // TODO(https://fxbug.dev/42143639): Distinguish between clients in RENEWING and REBINDING states
1484    if server_id.is_some() && msg.ciaddr.is_unspecified() && requested_ip.is_some() {
1485        Ok(ClientState::Selecting)
1486    } else if server_id.is_none() && requested_ip.is_some() && msg.ciaddr.is_unspecified() {
1487        Ok(ClientState::InitReboot)
1488    } else if server_id.is_none() && requested_ip.is_none() && !msg.ciaddr.is_unspecified() {
1489        Ok(ClientState::Renewing)
1490    } else {
1491        Err(())
1492    }
1493}
1494
1495fn get_requested_ip_addr(req: &Message) -> Option<Ipv4Addr> {
1496    req.options.iter().find_map(|opt| {
1497        if let DhcpOption::RequestedIpAddress(addr) = opt { Some(*addr) } else { None }
1498    })
1499}
1500
1501enum NakReason {
1502    ClientValidationFailure(ServerError),
1503    DifferentSubnets,
1504}
1505
1506impl std::fmt::Display for NakReason {
1507    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1508        match self {
1509            Self::ClientValidationFailure(e) => {
1510                write!(f, "requested ip is not assigned to client: {}", e)
1511            }
1512            Self::DifferentSubnets => {
1513                write!(f, "client and server are in different subnets")
1514            }
1515        }
1516    }
1517}
1518
1519pub fn get_server_id_from(req: &Message) -> Option<Ipv4Addr> {
1520    req.options.iter().find_map(|opt| match opt {
1521        DhcpOption::ServerIdentifier(addr) => Some(*addr),
1522        _ => None,
1523    })
1524}
1525
1526#[cfg(test)]
1527pub mod tests {
1528    use crate::configuration::{
1529        LeaseLength, ManagedAddresses, PermittedMacs, StaticAssignments, SubnetMask,
1530    };
1531    use crate::protocol::{
1532        DhcpOption, FidlCompatible as _, IntoFidlExt as _, Message, MessageType, OpCode,
1533        OptionCode, ProtocolError,
1534    };
1535    use crate::server::{
1536        AddressPool, AddressPoolError, ClientIdentifier, ClientState, DataStore, LeaseRecord,
1537        NakReason, OfferOptions, ResponseTarget, ServerAction, ServerDispatcher, ServerError,
1538        ServerParameters, SystemTimeSource, build_offer, get_client_state, options_repo,
1539        validate_discover,
1540    };
1541    use anyhow::Error;
1542    use assert_matches::assert_matches;
1543    use bstr::BString;
1544    use datastore::{ActionRecordingDataStore, DataStoreAction, FailingDataStore};
1545    use dhcp_protocol::{AtLeast, AtMostBytes};
1546    use fidl_fuchsia_net_ext::IntoExt as _;
1547    use net_declare::net::prefix_length_v4;
1548    use net_declare::{fidl_ip_v4, std_ip_v4};
1549    use net_types::ethernet::Mac as MacAddr;
1550    use net_types::ip::{Ipv4, PrefixLength};
1551    use std::cell::RefCell;
1552    use std::collections::{BTreeSet, HashMap, HashSet};
1553    use std::iter::FromIterator as _;
1554    use std::net::Ipv4Addr;
1555    use std::rc::Rc;
1556    use std::time::{Duration, SystemTime};
1557    use test_case::test_case;
1558    use zx::Status;
1559
1560    mod datastore {
1561        use crate::protocol::{DhcpOption, OptionCode};
1562        use crate::server::{
1563            ClientIdentifier, ClientRecords, DataStore, LeaseRecord, ServerParameters,
1564        };
1565        use std::collections::HashMap;
1566
1567        pub struct ActionRecordingDataStore {
1568            actions: Vec<DataStoreAction>,
1569        }
1570
1571        #[derive(Clone, Debug, PartialEq)]
1572        pub enum DataStoreAction {
1573            StoreClientRecord { client_id: ClientIdentifier, record: LeaseRecord },
1574            StoreOptions { opts: Vec<DhcpOption> },
1575            StoreParameters { params: ServerParameters },
1576            LoadClientRecords,
1577            LoadOptions,
1578            Delete { client_id: ClientIdentifier },
1579        }
1580
1581        #[derive(Debug, thiserror::Error)]
1582        #[error(transparent)]
1583        pub struct ActionRecordingError(#[from] anyhow::Error);
1584
1585        impl ActionRecordingDataStore {
1586            pub fn new() -> Self {
1587                Self { actions: Vec::new() }
1588            }
1589
1590            pub fn push_action(&mut self, cmd: DataStoreAction) -> () {
1591                let Self { actions } = self;
1592                actions.push(cmd)
1593            }
1594
1595            pub fn actions(&mut self) -> std::vec::Drain<'_, DataStoreAction> {
1596                let Self { actions } = self;
1597                actions.drain(..)
1598            }
1599
1600            pub fn load_client_records(&mut self) -> Result<ClientRecords, ActionRecordingError> {
1601                self.push_action(DataStoreAction::LoadClientRecords);
1602                Ok(HashMap::new())
1603            }
1604
1605            pub fn load_options(
1606                &mut self,
1607            ) -> Result<HashMap<OptionCode, DhcpOption>, ActionRecordingError> {
1608                self.push_action(DataStoreAction::LoadOptions);
1609                Ok(HashMap::new())
1610            }
1611        }
1612
1613        impl Drop for ActionRecordingDataStore {
1614            fn drop(&mut self) {
1615                let Self { actions } = self;
1616                assert!(actions.is_empty())
1617            }
1618        }
1619
1620        impl DataStore for ActionRecordingDataStore {
1621            type Error = ActionRecordingError;
1622
1623            fn insert(
1624                &mut self,
1625                client_id: &ClientIdentifier,
1626                record: &LeaseRecord,
1627            ) -> Result<(), Self::Error> {
1628                Ok(self.push_action(DataStoreAction::StoreClientRecord {
1629                    client_id: client_id.clone(),
1630                    record: record.clone(),
1631                }))
1632            }
1633
1634            fn store_options(&mut self, opts: &[DhcpOption]) -> Result<(), Self::Error> {
1635                Ok(self.push_action(DataStoreAction::StoreOptions { opts: Vec::from(opts) }))
1636            }
1637
1638            fn store_parameters(&mut self, params: &ServerParameters) -> Result<(), Self::Error> {
1639                Ok(self.push_action(DataStoreAction::StoreParameters { params: params.clone() }))
1640            }
1641
1642            fn delete(&mut self, client_id: &ClientIdentifier) -> Result<(), Self::Error> {
1643                Ok(self.push_action(DataStoreAction::Delete { client_id: client_id.clone() }))
1644            }
1645        }
1646
1647        /// A `DataStore` whose `insert` always fails, emulating e.g. a stash key which is too
1648        /// long to be encoded.
1649        pub struct FailingDataStore;
1650
1651        impl DataStore for FailingDataStore {
1652            type Error = ActionRecordingError;
1653
1654            fn insert(
1655                &mut self,
1656                _client_id: &ClientIdentifier,
1657                _record: &LeaseRecord,
1658            ) -> Result<(), Self::Error> {
1659                Err(ActionRecordingError(anyhow::anyhow!("insert failed")))
1660            }
1661
1662            fn store_options(&mut self, _opts: &[DhcpOption]) -> Result<(), Self::Error> {
1663                Err(ActionRecordingError(anyhow::anyhow!("store_options failed")))
1664            }
1665
1666            fn store_parameters(&mut self, _params: &ServerParameters) -> Result<(), Self::Error> {
1667                Err(ActionRecordingError(anyhow::anyhow!("store_parameters failed")))
1668            }
1669
1670            fn delete(&mut self, _client_id: &ClientIdentifier) -> Result<(), Self::Error> {
1671                Err(ActionRecordingError(anyhow::anyhow!("delete failed")))
1672            }
1673        }
1674    }
1675
1676    // UTC time can go backwards (https://fuchsia.dev/fuchsia-src/concepts/time/utc/behavior),
1677    // using `SystemTime::now` has the possibility to introduce flakiness to tests. This struct
1678    // makes sure we can get non-decreasing `SystemTime`s in a test environment.
1679    #[derive(Clone)]
1680    struct TestSystemTime(Rc<RefCell<SystemTime>>);
1681
1682    impl SystemTimeSource for TestSystemTime {
1683        fn with_current_time() -> Self {
1684            Self(Rc::new(RefCell::new(SystemTime::now())))
1685        }
1686        fn now(&self) -> SystemTime {
1687            let TestSystemTime(current) = self;
1688            *current.borrow()
1689        }
1690    }
1691
1692    impl TestSystemTime {
1693        pub(super) fn move_forward(&mut self, duration: Duration) {
1694            let TestSystemTime(current) = self;
1695            *current.borrow_mut() += duration;
1696        }
1697    }
1698
1699    type Server<DS = ActionRecordingDataStore> = super::Server<DS, TestSystemTime>;
1700
1701    fn default_server_params() -> Result<ServerParameters, Error> {
1702        test_server_params(
1703            Vec::new(),
1704            LeaseLength { default_seconds: 60 * 60 * 24, max_seconds: 60 * 60 * 24 * 7 },
1705        )
1706    }
1707
1708    fn test_server_params(
1709        server_ips: Vec<Ipv4Addr>,
1710        lease_length: LeaseLength,
1711    ) -> Result<ServerParameters, Error> {
1712        Ok(ServerParameters {
1713            server_ips,
1714            lease_length,
1715            managed_addrs: ManagedAddresses {
1716                mask: SubnetMask::new(prefix_length_v4!(24)),
1717                pool_range_start: net_declare::std::ip_v4!("192.168.0.0"),
1718                pool_range_stop: net_declare::std::ip_v4!("192.168.0.0"),
1719            },
1720            permitted_macs: PermittedMacs(Vec::new()),
1721            static_assignments: StaticAssignments(HashMap::new()),
1722            arp_probe: false,
1723            bound_device_names: Vec::new(),
1724        })
1725    }
1726
1727    pub fn random_ipv4_generator() -> Ipv4Addr {
1728        let octet1: u8 = rand::random();
1729        let octet2: u8 = rand::random();
1730        let octet3: u8 = rand::random();
1731        let octet4: u8 = rand::random();
1732        Ipv4Addr::new(octet1, octet2, octet3, octet4)
1733    }
1734
1735    pub fn random_mac_generator() -> MacAddr {
1736        let octet1: u8 = rand::random();
1737        let octet2: u8 = rand::random();
1738        let octet3: u8 = rand::random();
1739        let octet4: u8 = rand::random();
1740        let octet5: u8 = rand::random();
1741        let octet6: u8 = rand::random();
1742        MacAddr::new([octet1, octet2, octet3, octet4, octet5, octet6])
1743    }
1744
1745    fn extract_message(server_response: ServerAction) -> Message {
1746        if let ServerAction::SendResponse(message, _destination) = server_response {
1747            message
1748        } else {
1749            panic!("expected a message in server response, received {:?}", server_response)
1750        }
1751    }
1752
1753    fn get_router<DS: DataStore>(
1754        server: &Server<DS>,
1755    ) -> Result<
1756        AtLeast<1, AtMostBytes<{ dhcp_protocol::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>,
1757        ProtocolError,
1758    > {
1759        let code = OptionCode::Router;
1760        match server.options_repo.get(&code) {
1761            Some(DhcpOption::Router(router)) => Some(router.clone()),
1762            option => panic!("unexpected entry {} => {:?}", &code, option),
1763        }
1764        .ok_or(ProtocolError::MissingOption(code))
1765    }
1766
1767    fn get_dns_server<DS: DataStore>(
1768        server: &Server<DS>,
1769    ) -> Result<
1770        AtLeast<1, AtMostBytes<{ dhcp_protocol::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>,
1771        ProtocolError,
1772    > {
1773        let code = OptionCode::DomainNameServer;
1774        match server.options_repo.get(&code) {
1775            Some(DhcpOption::DomainNameServer(dns_server)) => Some(dns_server.clone()),
1776            option => panic!("unexpected entry {} => {:?}", &code, option),
1777        }
1778        .ok_or(ProtocolError::MissingOption(code))
1779    }
1780
1781    fn new_test_minimal_server_with_store<DS: DataStore>(
1782        store: DS,
1783    ) -> (Server<DS>, TestSystemTime) {
1784        let time_source = TestSystemTime::with_current_time();
1785        let params = test_server_params(
1786            vec![random_ipv4_generator()],
1787            LeaseLength { default_seconds: 100, max_seconds: 60 * 60 * 24 * 7 },
1788        )
1789        .expect("failed to create test server parameters");
1790        (
1791            super::Server {
1792                records: HashMap::new(),
1793                pool: AddressPool::new(params.managed_addrs.pool_range()),
1794                params,
1795                store: Some(store),
1796                options_repo: HashMap::from_iter(vec![
1797                    (OptionCode::Router, DhcpOption::Router([random_ipv4_generator()].into())),
1798                    (
1799                        OptionCode::DomainNameServer,
1800                        DhcpOption::DomainNameServer(
1801                            [std_ip_v4!("1.2.3.4"), std_ip_v4!("4.3.2.1")].into(),
1802                        ),
1803                    ),
1804                ]),
1805                time_source: time_source.clone(),
1806            },
1807            time_source.clone(),
1808        )
1809    }
1810
1811    fn new_test_minimal_server_with_time_source() -> (Server, TestSystemTime) {
1812        new_test_minimal_server_with_store(ActionRecordingDataStore::new())
1813    }
1814
1815    fn new_test_minimal_server() -> Server {
1816        let (server, _time_source) = new_test_minimal_server_with_time_source();
1817        server
1818    }
1819
1820    fn new_client_message(message_type: MessageType) -> Message {
1821        new_client_message_with_preset_options(message_type, std::iter::empty())
1822    }
1823
1824    fn new_client_message_with_preset_options(
1825        message_type: MessageType,
1826        options: impl Iterator<Item = DhcpOption>,
1827    ) -> Message {
1828        new_client_message_with_options(
1829            [
1830                DhcpOption::DhcpMessageType(message_type),
1831                DhcpOption::ParameterRequestList(
1832                    [OptionCode::SubnetMask, OptionCode::Router, OptionCode::DomainNameServer]
1833                        .into(),
1834                ),
1835            ]
1836            .into_iter()
1837            .chain(options),
1838        )
1839    }
1840
1841    fn new_client_message_with_options<T: IntoIterator<Item = DhcpOption>>(options: T) -> Message {
1842        Message {
1843            op: OpCode::BOOTREQUEST,
1844            xid: rand::random(),
1845            secs: 0,
1846            bdcast_flag: true,
1847            ciaddr: Ipv4Addr::UNSPECIFIED,
1848            yiaddr: Ipv4Addr::UNSPECIFIED,
1849            siaddr: Ipv4Addr::UNSPECIFIED,
1850            giaddr: Ipv4Addr::UNSPECIFIED,
1851            chaddr: random_mac_generator(),
1852            sname: BString::default(),
1853            file: BString::default(),
1854            options: options.into_iter().collect(),
1855        }
1856    }
1857
1858    fn new_test_discover() -> Message {
1859        new_test_discover_with_options(std::iter::empty())
1860    }
1861
1862    fn new_test_discover_with_options(options: impl Iterator<Item = DhcpOption>) -> Message {
1863        new_client_message_with_preset_options(MessageType::DHCPDISCOVER, options)
1864    }
1865
1866    fn new_server_message<DS: DataStore>(
1867        message_type: MessageType,
1868        client_message: &Message,
1869        server: &Server<DS>,
1870    ) -> Message {
1871        let Message {
1872            op: _,
1873            xid,
1874            secs: _,
1875            bdcast_flag,
1876            ciaddr: _,
1877            yiaddr: _,
1878            siaddr: _,
1879            giaddr: _,
1880            chaddr,
1881            sname: _,
1882            file: _,
1883            options: _,
1884        } = client_message;
1885        Message {
1886            op: OpCode::BOOTREPLY,
1887            xid: *xid,
1888            secs: 0,
1889            bdcast_flag: *bdcast_flag,
1890            ciaddr: Ipv4Addr::UNSPECIFIED,
1891            yiaddr: Ipv4Addr::UNSPECIFIED,
1892            siaddr: Ipv4Addr::UNSPECIFIED,
1893            giaddr: Ipv4Addr::UNSPECIFIED,
1894            chaddr: *chaddr,
1895            sname: BString::default(),
1896            file: BString::default(),
1897            options: vec![
1898                DhcpOption::DhcpMessageType(message_type),
1899                DhcpOption::ServerIdentifier(
1900                    server.get_server_ip(client_message).unwrap_or(Ipv4Addr::UNSPECIFIED),
1901                ),
1902            ],
1903        }
1904    }
1905
1906    fn new_server_message_with_lease<DS: DataStore>(
1907        message_type: MessageType,
1908        client_message: &Message,
1909        server: &Server<DS>,
1910    ) -> Message {
1911        let mut msg = new_server_message(message_type, client_message, server);
1912        msg.options.extend([
1913            DhcpOption::IpAddressLeaseTime(100),
1914            DhcpOption::RenewalTimeValue(50),
1915            DhcpOption::RebindingTimeValue(75),
1916        ]);
1917        add_server_options(&mut msg, server);
1918        msg
1919    }
1920
1921    const DEFAULT_PREFIX_LENGTH: PrefixLength<Ipv4> = prefix_length_v4!(24);
1922
1923    fn add_server_options<DS: DataStore>(msg: &mut Message, server: &Server<DS>) {
1924        msg.options.push(DhcpOption::SubnetMask(DEFAULT_PREFIX_LENGTH));
1925        if let Some(routers) = match server.options_repo.get(&OptionCode::Router) {
1926            Some(DhcpOption::Router(v)) => Some(v),
1927            _ => None,
1928        } {
1929            msg.options.push(DhcpOption::Router(routers.clone()));
1930        }
1931        if let Some(servers) = match server.options_repo.get(&OptionCode::DomainNameServer) {
1932            Some(DhcpOption::DomainNameServer(v)) => Some(v),
1933            _ => None,
1934        } {
1935            msg.options.push(DhcpOption::DomainNameServer(servers.clone()));
1936        }
1937    }
1938
1939    fn new_test_offer<DS: DataStore>(disc: &Message, server: &Server<DS>) -> Message {
1940        new_server_message_with_lease(MessageType::DHCPOFFER, disc, server)
1941    }
1942
1943    fn new_test_request() -> Message {
1944        new_client_message(MessageType::DHCPREQUEST)
1945    }
1946
1947    fn new_test_request_selecting_state<DS: DataStore>(
1948        server: &Server<DS>,
1949        requested_ip: Ipv4Addr,
1950    ) -> Message {
1951        let mut req = new_test_request();
1952        req.options.push(DhcpOption::RequestedIpAddress(requested_ip));
1953        req.options.push(DhcpOption::ServerIdentifier(
1954            server.get_server_ip(&req).unwrap_or(Ipv4Addr::UNSPECIFIED),
1955        ));
1956        req
1957    }
1958
1959    fn new_test_ack<DS: DataStore>(req: &Message, server: &Server<DS>) -> Message {
1960        new_server_message_with_lease(MessageType::DHCPACK, req, server)
1961    }
1962
1963    fn new_test_nak<DS: DataStore>(
1964        req: &Message,
1965        server: &Server<DS>,
1966        reason: NakReason,
1967    ) -> Message {
1968        let mut nak = new_server_message(MessageType::DHCPNAK, req, server);
1969        nak.options.push(DhcpOption::Message(format!("{}", reason)));
1970        nak
1971    }
1972
1973    fn new_test_release() -> Message {
1974        new_client_message(MessageType::DHCPRELEASE)
1975    }
1976
1977    fn new_test_inform() -> Message {
1978        new_client_message(MessageType::DHCPINFORM)
1979    }
1980
1981    fn new_test_inform_ack<DS: DataStore>(req: &Message, server: &Server<DS>) -> Message {
1982        let mut msg = new_server_message(MessageType::DHCPACK, req, server);
1983        add_server_options(&mut msg, server);
1984        msg
1985    }
1986
1987    fn new_test_decline<DS: DataStore>(server: &Server<DS>) -> Message {
1988        let mut decline = new_client_message(MessageType::DHCPDECLINE);
1989        decline.options.push(DhcpOption::ServerIdentifier(
1990            server.get_server_ip(&decline).unwrap_or(Ipv4Addr::UNSPECIFIED),
1991        ));
1992        decline
1993    }
1994
1995    #[test]
1996    fn dispatch_with_discover_returns_correct_offer_and_dest_giaddr_when_giaddr_set() {
1997        let mut server = new_test_minimal_server();
1998        let mut disc = new_test_discover();
1999        disc.giaddr = random_ipv4_generator();
2000        let client_id = ClientIdentifier::from(&disc);
2001
2002        let offer_ip = random_ipv4_generator();
2003
2004        assert!(server.pool.universe.insert(offer_ip));
2005
2006        let mut expected_offer = new_test_offer(&disc, &server);
2007        expected_offer.yiaddr = offer_ip;
2008        expected_offer.giaddr = disc.giaddr;
2009
2010        let expected_dest = disc.giaddr;
2011
2012        assert_eq!(
2013            server.dispatch(disc),
2014            Ok(ServerAction::SendResponse(
2015                expected_offer,
2016                ResponseTarget::Unicast(expected_dest, None)
2017            ))
2018        );
2019        assert_matches::assert_matches!(
2020            server.store.expect("missing store").actions().as_slice(),
2021            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
2022        );
2023    }
2024
2025    #[test]
2026    fn dispatch_with_discover_returns_correct_offer_and_dest_broadcast_when_giaddr_unspecified() {
2027        let mut server = new_test_minimal_server();
2028        let disc = new_test_discover();
2029        let client_id = ClientIdentifier::from(&disc);
2030
2031        let offer_ip = random_ipv4_generator();
2032        assert!(server.pool.universe.insert(offer_ip));
2033        let expected_offer = {
2034            let mut expected_offer = new_test_offer(&disc, &server);
2035            expected_offer.yiaddr = offer_ip;
2036            expected_offer
2037        };
2038
2039        assert_eq!(
2040            server.dispatch(disc),
2041            Ok(ServerAction::SendResponse(expected_offer, ResponseTarget::Broadcast))
2042        );
2043        assert_matches::assert_matches!(
2044            server.store.expect("missing store").actions().as_slice(),
2045            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
2046        );
2047    }
2048
2049    #[test]
2050    fn dispatch_with_discover_returns_correct_offer_and_dest_yiaddr_when_giaddr_and_ciaddr_unspecified_and_broadcast_bit_unset()
2051     {
2052        let mut server = new_test_minimal_server();
2053        let disc = {
2054            let mut disc = new_test_discover();
2055            disc.bdcast_flag = false;
2056            disc
2057        };
2058        let chaddr = disc.chaddr;
2059        let client_id = ClientIdentifier::from(&disc);
2060
2061        let offer_ip = random_ipv4_generator();
2062        assert!(server.pool.universe.insert(offer_ip));
2063        let expected_offer = {
2064            let mut expected_offer = new_test_offer(&disc, &server);
2065            expected_offer.yiaddr = offer_ip;
2066            expected_offer
2067        };
2068
2069        assert_eq!(
2070            server.dispatch(disc),
2071            Ok(ServerAction::SendResponse(
2072                expected_offer,
2073                ResponseTarget::Unicast(offer_ip, Some(chaddr))
2074            ))
2075        );
2076        assert_matches::assert_matches!(
2077            server.store.expect("missing store").actions().as_slice(),
2078            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
2079        );
2080    }
2081
2082    #[test]
2083    fn dispatch_with_discover_returns_correct_offer_and_dest_giaddr_if_giaddr_broadcast_bit_is_set()
2084    {
2085        let mut server = new_test_minimal_server();
2086        let giaddr = random_ipv4_generator();
2087        let disc = {
2088            let mut disc = new_test_discover();
2089            disc.giaddr = giaddr;
2090            disc
2091        };
2092        let client_id = ClientIdentifier::from(&disc);
2093
2094        let offer_ip = random_ipv4_generator();
2095        assert!(server.pool.universe.insert(offer_ip));
2096
2097        let expected_offer = {
2098            let mut expected_offer = new_test_offer(&disc, &server);
2099            expected_offer.yiaddr = offer_ip;
2100            expected_offer.giaddr = giaddr;
2101            expected_offer
2102        };
2103
2104        assert_eq!(
2105            server.dispatch(disc),
2106            Ok(ServerAction::SendResponse(expected_offer, ResponseTarget::Unicast(giaddr, None)))
2107        );
2108        assert_matches::assert_matches!(
2109            server.store.expect("missing store").actions().as_slice(),
2110            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
2111        );
2112    }
2113
2114    #[test]
2115    fn dispatch_with_discover_returns_error_if_ciaddr_set() {
2116        use std::string::ToString as _;
2117        let mut server = new_test_minimal_server();
2118        let ciaddr = random_ipv4_generator();
2119        let disc = {
2120            let mut disc = new_test_discover();
2121            disc.ciaddr = ciaddr;
2122            disc
2123        };
2124
2125        assert!(server.pool.universe.insert(random_ipv4_generator()));
2126
2127        assert_eq!(
2128            server.dispatch(disc),
2129            Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
2130                field: String::from("ciaddr"),
2131                value: ciaddr.to_string(),
2132                msg_type: MessageType::DHCPDISCOVER
2133            }))
2134        );
2135    }
2136
2137    #[test]
2138    fn dispatch_with_discover_updates_server_state() {
2139        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2140        let disc = new_test_discover();
2141
2142        let offer_ip = random_ipv4_generator();
2143        let client_id = ClientIdentifier::from(&disc);
2144
2145        assert!(server.pool.universe.insert(offer_ip));
2146
2147        let server_id = server.params.server_ips.first().unwrap();
2148        let router = get_router(&server).expect("failed to get router");
2149        let dns_server = get_dns_server(&server).expect("failed to get dns server");
2150        let expected_client_record = LeaseRecord::new(
2151            Some(offer_ip),
2152            vec![
2153                DhcpOption::ServerIdentifier(*server_id),
2154                DhcpOption::IpAddressLeaseTime(server.params.lease_length.default_seconds),
2155                DhcpOption::RenewalTimeValue(server.params.lease_length.default_seconds / 2),
2156                DhcpOption::RebindingTimeValue(
2157                    (server.params.lease_length.default_seconds * 3) / 4,
2158                ),
2159                DhcpOption::SubnetMask(DEFAULT_PREFIX_LENGTH),
2160                DhcpOption::Router(router),
2161                DhcpOption::DomainNameServer(dns_server),
2162            ],
2163            time_source.now(),
2164            server.params.lease_length.default_seconds,
2165        )
2166        .expect("failed to create lease record");
2167
2168        let _response = server.dispatch(disc);
2169
2170        let available: Vec<_> = server.pool.available().collect();
2171        assert!(available.is_empty(), "{:?}", available);
2172        assert_eq!(server.pool.allocated.len(), 1);
2173        assert_eq!(server.records.len(), 1);
2174        assert_eq!(server.records.get(&client_id), Some(&expected_client_record));
2175        assert_matches::assert_matches!(
2176            server.store.expect("missing store").actions().as_slice(),
2177            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
2178        );
2179    }
2180
2181    // Regression test for https://fxbug.dev/518910836: if the data store rejects the client
2182    // record, the offered address must be returned to the pool. Otherwise the address is leaked
2183    // forever, since expired lease reclamation only considers addresses which have a lease
2184    // record.
2185    #[test]
2186    fn dispatch_with_discover_data_store_failure_does_not_leak_addr() {
2187        let (mut server, _time_source) = new_test_minimal_server_with_store(FailingDataStore);
2188        let disc = new_test_discover();
2189        let client_id = ClientIdentifier::from(&disc);
2190
2191        let offer_ip = random_ipv4_generator();
2192        assert!(server.pool.universe.insert(offer_ip));
2193        let available_before: Vec<_> = server.pool.available().collect();
2194
2195        assert_matches!(server.dispatch(disc), Err(ServerError::DataStoreUpdateFailure(_)));
2196
2197        assert_eq!(server.records.get(&client_id), None);
2198        assert!(!server.pool.addr_is_allocated(offer_ip));
2199        let available_after: Vec<_> = server.pool.available().collect();
2200        assert_eq!(available_before, available_after);
2201        assert!(server.pool.allocated.is_empty(), "{:?}", server.pool.allocated);
2202    }
2203
2204    fn dispatch_with_discover_updates_stash_helper(
2205        additional_options: impl Iterator<Item = DhcpOption>,
2206    ) {
2207        let mut server = new_test_minimal_server();
2208        let disc = new_test_discover_with_options(additional_options);
2209
2210        let client_id = ClientIdentifier::from(&disc);
2211
2212        assert!(server.pool.universe.insert(random_ipv4_generator()));
2213
2214        let server_action = server.dispatch(disc);
2215        assert!(server_action.is_ok());
2216
2217        let client_record = server
2218            .records
2219            .get(&client_id)
2220            .unwrap_or_else(|| panic!("server records missing entry for {}", client_id))
2221            .clone();
2222        assert_matches::assert_matches!(
2223            server.store.expect("missing store").actions().as_slice(),
2224            [
2225                DataStoreAction::StoreClientRecord { client_id: id, record },
2226            ] if *id == client_id && *record == client_record
2227        );
2228    }
2229
2230    #[test]
2231    fn dispatch_with_discover_updates_stash() {
2232        dispatch_with_discover_updates_stash_helper(std::iter::empty())
2233    }
2234
2235    #[test]
2236    fn dispatch_with_discover_with_client_id_updates_stash() {
2237        dispatch_with_discover_updates_stash_helper(std::iter::once(DhcpOption::ClientIdentifier(
2238            [1, 2, 3, 4, 5].into(),
2239        )))
2240    }
2241
2242    #[test]
2243    fn dispatch_with_discover_client_binding_returns_bound_addr() {
2244        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2245        let disc = new_test_discover();
2246        let client_id = ClientIdentifier::from(&disc);
2247
2248        let bound_client_ip = random_ipv4_generator();
2249
2250        assert!(server.pool.allocated.insert(bound_client_ip));
2251        assert!(server.pool.universe.insert(bound_client_ip));
2252
2253        assert_matches::assert_matches!(
2254            server.records.insert(
2255                ClientIdentifier::from(&disc),
2256                LeaseRecord::new(Some(bound_client_ip), Vec::new(), time_source.now(), u32::MAX)
2257                    .expect("failed to create lease record"),
2258            ),
2259            None
2260        );
2261
2262        let response = server.dispatch(disc).unwrap();
2263
2264        assert_eq!(extract_message(response).yiaddr, bound_client_ip);
2265        assert_matches::assert_matches!(
2266            server.store.expect("missing store").actions().as_slice(),
2267            [
2268                DataStoreAction::StoreClientRecord {client_id: id, record: LeaseRecord {current: Some(ip), previous: None, .. }},
2269            ] if *id == client_id && *ip == bound_client_ip
2270        );
2271    }
2272
2273    #[test]
2274    #[should_panic(expected = "active lease is unallocated in address pool")]
2275    fn dispatch_with_discover_client_binding_panics_when_addr_previously_not_allocated() {
2276        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2277        let disc = new_test_discover();
2278
2279        let bound_client_ip = random_ipv4_generator();
2280
2281        assert!(server.pool.universe.insert(bound_client_ip));
2282
2283        assert_matches::assert_matches!(
2284            server.records.insert(
2285                ClientIdentifier::from(&disc),
2286                LeaseRecord::new(Some(bound_client_ip), Vec::new(), time_source.now(), u32::MAX)
2287                    .unwrap(),
2288            ),
2289            None
2290        );
2291
2292        let _ = server.dispatch(disc);
2293    }
2294
2295    #[test]
2296    fn dispatch_with_discover_expired_client_binding_returns_available_old_addr() {
2297        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2298        let disc = new_test_discover();
2299        let client_id = ClientIdentifier::from(&disc);
2300
2301        let bound_client_ip = random_ipv4_generator();
2302
2303        assert!(server.pool.universe.insert(bound_client_ip));
2304
2305        assert_matches::assert_matches!(
2306            server.records.insert(
2307                ClientIdentifier::from(&disc),
2308                // Manually initialize because new() assumes an unexpired lease.
2309                LeaseRecord {
2310                    current: None,
2311                    previous: Some(bound_client_ip),
2312                    options: Vec::new(),
2313                    lease_start_epoch_seconds: time_source
2314                        .now()
2315                        .duration_since(std::time::UNIX_EPOCH)
2316                        .expect("invalid time value")
2317                        .as_secs(),
2318                    lease_length_seconds: u32::MIN
2319                },
2320            ),
2321            None
2322        );
2323
2324        let response = server.dispatch(disc).unwrap();
2325
2326        assert_eq!(extract_message(response).yiaddr, bound_client_ip);
2327        assert_matches::assert_matches!(
2328            server.store.expect("missing store").actions().as_slice(),
2329            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
2330        );
2331    }
2332
2333    #[test]
2334    fn dispatch_with_discover_expired_client_binding_unavailable_addr_returns_next_free_addr() {
2335        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2336        let disc = new_test_discover();
2337        let client_id = ClientIdentifier::from(&disc);
2338
2339        let bound_client_ip = random_ipv4_generator();
2340        let free_ip = random_ipv4_generator();
2341
2342        assert!(server.pool.allocated.insert(bound_client_ip));
2343        assert!(server.pool.universe.insert(free_ip));
2344
2345        assert_matches::assert_matches!(
2346            server.records.insert(
2347                ClientIdentifier::from(&disc),
2348                // Manually initialize because new() assumes an unexpired lease.
2349                LeaseRecord {
2350                    current: None,
2351                    previous: Some(bound_client_ip),
2352                    options: Vec::new(),
2353                    lease_start_epoch_seconds: time_source
2354                        .now()
2355                        .duration_since(std::time::UNIX_EPOCH)
2356                        .expect("invalid time value")
2357                        .as_secs(),
2358                    lease_length_seconds: u32::MIN
2359                },
2360            ),
2361            None
2362        );
2363
2364        let response = server.dispatch(disc).unwrap();
2365
2366        assert_eq!(extract_message(response).yiaddr, free_ip);
2367        assert_matches::assert_matches!(
2368            server.store.expect("missing store").actions().as_slice(),
2369            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
2370        );
2371    }
2372
2373    #[test]
2374    fn dispatch_with_discover_expired_client_binding_returns_available_requested_addr() {
2375        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2376        let mut disc = new_test_discover();
2377        let client_id = ClientIdentifier::from(&disc);
2378
2379        let bound_client_ip = random_ipv4_generator();
2380        let requested_ip = random_ipv4_generator();
2381
2382        assert!(server.pool.allocated.insert(bound_client_ip));
2383        assert!(server.pool.universe.insert(requested_ip));
2384
2385        disc.options.push(DhcpOption::RequestedIpAddress(requested_ip));
2386
2387        assert_matches::assert_matches!(
2388            server.records.insert(
2389                ClientIdentifier::from(&disc),
2390                // Manually initialize because new() assumes an unexpired lease.
2391                LeaseRecord {
2392                    current: None,
2393                    previous: Some(bound_client_ip),
2394                    options: Vec::new(),
2395                    lease_start_epoch_seconds: time_source
2396                        .now()
2397                        .duration_since(std::time::UNIX_EPOCH)
2398                        .expect("invalid time value")
2399                        .as_secs(),
2400                    lease_length_seconds: u32::MIN
2401                },
2402            ),
2403            None
2404        );
2405
2406        let response = server.dispatch(disc).unwrap();
2407
2408        assert_eq!(extract_message(response).yiaddr, requested_ip);
2409        assert_matches::assert_matches!(
2410            server.store.expect("missing store").actions().as_slice(),
2411            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
2412        );
2413    }
2414
2415    #[test]
2416    fn dispatch_with_discover_expired_client_binding_returns_next_addr_for_unavailable_requested_addr()
2417     {
2418        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2419        let mut disc = new_test_discover();
2420        let client_id = ClientIdentifier::from(&disc);
2421
2422        let bound_client_ip = random_ipv4_generator();
2423        let requested_ip = random_ipv4_generator();
2424        let free_ip = random_ipv4_generator();
2425
2426        assert!(server.pool.allocated.insert(bound_client_ip));
2427        assert!(server.pool.allocated.insert(requested_ip));
2428        assert!(server.pool.universe.insert(free_ip));
2429
2430        disc.options.push(DhcpOption::RequestedIpAddress(requested_ip));
2431
2432        assert_matches::assert_matches!(
2433            server.records.insert(
2434                ClientIdentifier::from(&disc),
2435                // Manually initialize because new() assumes an unexpired lease.
2436                LeaseRecord {
2437                    current: None,
2438                    previous: Some(bound_client_ip),
2439                    options: Vec::new(),
2440                    lease_start_epoch_seconds: time_source
2441                        .now()
2442                        .duration_since(std::time::UNIX_EPOCH)
2443                        .expect("invalid time value")
2444                        .as_secs(),
2445                    lease_length_seconds: u32::MIN
2446                },
2447            ),
2448            None
2449        );
2450
2451        let response = server.dispatch(disc).unwrap();
2452
2453        assert_eq!(extract_message(response).yiaddr, free_ip);
2454        assert_matches::assert_matches!(
2455            server.store.expect("missing store").actions().as_slice(),
2456            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
2457        );
2458    }
2459
2460    #[test]
2461    fn dispatch_with_discover_available_requested_addr_returns_requested_addr() {
2462        let mut server = new_test_minimal_server();
2463        let mut disc = new_test_discover();
2464        let client_id = ClientIdentifier::from(&disc);
2465
2466        let requested_ip = random_ipv4_generator();
2467        let free_ip_1 = random_ipv4_generator();
2468        let free_ip_2 = random_ipv4_generator();
2469
2470        assert!(server.pool.universe.insert(free_ip_1));
2471        assert!(server.pool.universe.insert(requested_ip));
2472        assert!(server.pool.universe.insert(free_ip_2));
2473
2474        // Update discover message to request for a specific ip
2475        // which is available in server pool.
2476        disc.options.push(DhcpOption::RequestedIpAddress(requested_ip));
2477
2478        let response = server.dispatch(disc).unwrap();
2479
2480        assert_eq!(extract_message(response).yiaddr, requested_ip);
2481        assert_matches::assert_matches!(
2482            server.store.expect("missing store").actions().as_slice(),
2483            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
2484        );
2485    }
2486
2487    #[test]
2488    fn dispatch_with_discover_unavailable_requested_addr_returns_next_free_addr() {
2489        let mut server = new_test_minimal_server();
2490        let mut disc = new_test_discover();
2491        let client_id = ClientIdentifier::from(&disc);
2492
2493        let requested_ip = random_ipv4_generator();
2494        let free_ip_1 = random_ipv4_generator();
2495
2496        assert!(server.pool.allocated.insert(requested_ip));
2497        assert!(server.pool.universe.insert(free_ip_1));
2498
2499        disc.options.push(DhcpOption::RequestedIpAddress(requested_ip));
2500
2501        let response = server.dispatch(disc).unwrap();
2502
2503        assert_eq!(extract_message(response).yiaddr, free_ip_1);
2504        assert_matches::assert_matches!(
2505            server.store.expect("missing store").actions().as_slice(),
2506            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
2507        );
2508    }
2509
2510    #[test]
2511    fn dispatch_with_discover_unavailable_requested_addr_no_available_addr_returns_error() {
2512        let mut server = new_test_minimal_server();
2513        let mut disc = new_test_discover();
2514
2515        let requested_ip = random_ipv4_generator();
2516
2517        assert!(server.pool.allocated.insert(requested_ip));
2518
2519        disc.options.push(DhcpOption::RequestedIpAddress(requested_ip));
2520
2521        assert_eq!(
2522            server.dispatch(disc),
2523            Err(ServerError::ServerAddressPoolFailure(AddressPoolError::Ipv4AddrExhaustion))
2524        );
2525    }
2526
2527    #[test]
2528    fn dispatch_with_discover_no_requested_addr_no_available_addr_returns_error() {
2529        let mut server = new_test_minimal_server();
2530        let disc = new_test_discover();
2531        server.pool.universe.clear();
2532
2533        assert_eq!(
2534            server.dispatch(disc),
2535            Err(ServerError::ServerAddressPoolFailure(AddressPoolError::Ipv4AddrExhaustion))
2536        );
2537    }
2538
2539    fn test_dispatch_with_bogus_client_message_returns_error(message_type: MessageType) {
2540        let mut server = new_test_minimal_server();
2541
2542        assert_eq!(
2543            server.dispatch(Message {
2544                op: OpCode::BOOTREQUEST,
2545                xid: 0,
2546                secs: 0,
2547                bdcast_flag: false,
2548                ciaddr: Ipv4Addr::UNSPECIFIED,
2549                yiaddr: Ipv4Addr::UNSPECIFIED,
2550                siaddr: Ipv4Addr::UNSPECIFIED,
2551                giaddr: Ipv4Addr::UNSPECIFIED,
2552                chaddr: MacAddr::new([0; 6]),
2553                sname: BString::default(),
2554                file: BString::default(),
2555                options: vec![DhcpOption::DhcpMessageType(message_type),],
2556            }),
2557            Err(ServerError::UnexpectedClientMessageType(message_type))
2558        );
2559    }
2560
2561    #[test]
2562    fn dispatch_with_client_offer_message_returns_error() {
2563        test_dispatch_with_bogus_client_message_returns_error(MessageType::DHCPOFFER)
2564    }
2565
2566    #[test]
2567    fn dispatch_with_client_ack_message_returns_error() {
2568        test_dispatch_with_bogus_client_message_returns_error(MessageType::DHCPACK)
2569    }
2570
2571    #[test]
2572    fn dispatch_with_client_nak_message_returns_error() {
2573        test_dispatch_with_bogus_client_message_returns_error(MessageType::DHCPNAK)
2574    }
2575
2576    #[test]
2577    fn dispatch_with_selecting_request_returns_correct_ack() {
2578        test_selecting(true)
2579    }
2580
2581    #[test]
2582    fn dispatch_with_selecting_request_bdcast_unset_returns_unicast_ack() {
2583        test_selecting(false)
2584    }
2585
2586    fn test_selecting(broadcast: bool) {
2587        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2588        let requested_ip = random_ipv4_generator();
2589        let req = {
2590            let mut req = new_test_request_selecting_state(&server, requested_ip);
2591            req.bdcast_flag = broadcast;
2592            req
2593        };
2594
2595        assert!(server.pool.allocated.insert(requested_ip));
2596
2597        let server_id = server.params.server_ips.first().unwrap();
2598        let router = get_router(&server).expect("failed to get router from server");
2599        let dns_server = get_dns_server(&server).expect("failed to get dns server from the server");
2600        assert_matches::assert_matches!(
2601            server.records.insert(
2602                ClientIdentifier::from(&req),
2603                LeaseRecord::new(
2604                    Some(requested_ip),
2605                    vec![
2606                        DhcpOption::ServerIdentifier(*server_id),
2607                        DhcpOption::IpAddressLeaseTime(server.params.lease_length.default_seconds),
2608                        DhcpOption::RenewalTimeValue(
2609                            server.params.lease_length.default_seconds / 2
2610                        ),
2611                        DhcpOption::RebindingTimeValue(
2612                            (server.params.lease_length.default_seconds * 3) / 4,
2613                        ),
2614                        DhcpOption::SubnetMask(DEFAULT_PREFIX_LENGTH),
2615                        DhcpOption::Router(router),
2616                        DhcpOption::DomainNameServer(dns_server),
2617                    ],
2618                    time_source.now(),
2619                    u32::MAX,
2620                )
2621                .expect("failed to create lease record"),
2622            ),
2623            None
2624        );
2625
2626        let mut expected_ack = new_test_ack(&req, &server);
2627        expected_ack.yiaddr = requested_ip;
2628        let expected_response = if broadcast {
2629            Ok(ServerAction::SendResponse(expected_ack, ResponseTarget::Broadcast))
2630        } else {
2631            Ok(ServerAction::SendResponse(
2632                expected_ack,
2633                ResponseTarget::Unicast(requested_ip, Some(req.chaddr)),
2634            ))
2635        };
2636        assert_eq!(server.dispatch(req), expected_response,);
2637    }
2638
2639    #[test]
2640    fn dispatch_with_selecting_request_maintains_server_invariants() {
2641        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2642        let requested_ip = random_ipv4_generator();
2643        let req = new_test_request_selecting_state(&server, requested_ip);
2644
2645        let client_id = ClientIdentifier::from(&req);
2646
2647        assert!(server.pool.allocated.insert(requested_ip));
2648        assert_matches::assert_matches!(
2649            server.records.insert(
2650                client_id.clone(),
2651                LeaseRecord::new(Some(requested_ip), Vec::new(), time_source.now(), u32::MAX)
2652                    .expect("failed to create lease record"),
2653            ),
2654            None
2655        );
2656        let _response = server.dispatch(req).unwrap();
2657        assert!(server.records.contains_key(&client_id));
2658        assert!(server.pool.addr_is_allocated(requested_ip));
2659    }
2660
2661    #[test]
2662    fn dispatch_with_selecting_request_wrong_server_ip_returns_error() {
2663        let mut server = new_test_minimal_server();
2664        let mut req = new_test_request_selecting_state(&server, random_ipv4_generator());
2665
2666        // Update request to have a server ip different from actual server ip.
2667        assert_matches::assert_matches!(
2668            req.options.remove(req.options.len() - 1),
2669            DhcpOption::ServerIdentifier { .. }
2670        );
2671        req.options.push(DhcpOption::ServerIdentifier(random_ipv4_generator()));
2672
2673        let server_ip = *server.params.server_ips.first().expect("server missing IP address");
2674        assert_eq!(server.dispatch(req), Err(ServerError::IncorrectDHCPServer(server_ip)));
2675    }
2676
2677    #[test]
2678    fn dispatch_with_selecting_request_unknown_client_mac_returns_nak_maintains_server_invariants()
2679    {
2680        let mut server = new_test_minimal_server();
2681        let requested_ip = random_ipv4_generator();
2682        let req = new_test_request_selecting_state(&server, requested_ip);
2683
2684        let client_id = ClientIdentifier::from(&req);
2685
2686        let expected_nak = new_test_nak(
2687            &req,
2688            &server,
2689            NakReason::ClientValidationFailure(ServerError::UnknownClientId(client_id.clone())),
2690        );
2691        assert_eq!(
2692            server.dispatch(req),
2693            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
2694        );
2695        assert!(!server.records.contains_key(&client_id));
2696        assert!(!server.pool.addr_is_allocated(requested_ip));
2697    }
2698
2699    #[test]
2700    fn dispatch_with_selecting_request_mismatched_requested_addr_returns_nak() {
2701        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2702        let client_requested_ip = random_ipv4_generator();
2703        let req = new_test_request_selecting_state(&server, client_requested_ip);
2704
2705        let server_offered_ip = random_ipv4_generator();
2706
2707        assert!(server.pool.allocated.insert(server_offered_ip));
2708
2709        assert_matches::assert_matches!(
2710            server.records.insert(
2711                ClientIdentifier::from(&req),
2712                LeaseRecord::new(Some(server_offered_ip), Vec::new(), time_source.now(), u32::MAX,)
2713                    .expect("failed to create lease record"),
2714            ),
2715            None
2716        );
2717
2718        let expected_nak = new_test_nak(
2719            &req,
2720            &server,
2721            NakReason::ClientValidationFailure(ServerError::RequestedIpOfferIpMismatch(
2722                client_requested_ip,
2723                server_offered_ip,
2724            )),
2725        );
2726        assert_eq!(
2727            server.dispatch(req),
2728            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
2729        );
2730    }
2731
2732    #[test]
2733    fn dispatch_with_selecting_request_expired_client_binding_returns_nak() {
2734        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2735        let requested_ip = random_ipv4_generator();
2736        let req = new_test_request_selecting_state(&server, requested_ip);
2737
2738        assert!(server.pool.universe.insert(requested_ip));
2739        assert!(server.pool.allocated.insert(requested_ip));
2740
2741        assert_matches::assert_matches!(
2742            server.records.insert(
2743                ClientIdentifier::from(&req),
2744                LeaseRecord::new(Some(requested_ip), Vec::new(), time_source.now(), u32::MIN)
2745                    .expect("failed to create lease record"),
2746            ),
2747            None
2748        );
2749
2750        let expected_nak = new_test_nak(
2751            &req,
2752            &server,
2753            NakReason::ClientValidationFailure(ServerError::ExpiredLeaseRecord),
2754        );
2755        assert_eq!(
2756            server.dispatch(req),
2757            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
2758        );
2759    }
2760
2761    #[test]
2762    fn dispatch_with_selecting_request_no_reserved_addr_returns_nak() {
2763        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2764        let requested_ip = random_ipv4_generator();
2765        let req = new_test_request_selecting_state(&server, requested_ip);
2766
2767        assert_matches::assert_matches!(
2768            server.records.insert(
2769                ClientIdentifier::from(&req),
2770                LeaseRecord::new(Some(requested_ip), Vec::new(), time_source.now(), u32::MAX)
2771                    .expect("failed to create lese record"),
2772            ),
2773            None
2774        );
2775
2776        let expected_nak = new_test_nak(
2777            &req,
2778            &server,
2779            NakReason::ClientValidationFailure(ServerError::UnidentifiedRequestedIp(requested_ip)),
2780        );
2781        assert_eq!(
2782            server.dispatch(req),
2783            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
2784        );
2785    }
2786
2787    #[test]
2788    fn dispatch_with_init_boot_request_returns_correct_ack() {
2789        test_init_reboot(true)
2790    }
2791
2792    #[test]
2793    fn dispatch_with_init_boot_bdcast_unset_request_returns_correct_ack() {
2794        test_init_reboot(false)
2795    }
2796
2797    fn test_init_reboot(broadcast: bool) {
2798        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2799        let mut req = new_test_request();
2800        req.bdcast_flag = broadcast;
2801
2802        // For init-reboot, server and requested ip must be on the same subnet.
2803        // Hard-coding ip values here to achieve that.
2804        let init_reboot_client_ip = std_ip_v4!("192.168.1.60");
2805        server.params.server_ips = vec![std_ip_v4!("192.168.1.1")];
2806
2807        assert!(server.pool.allocated.insert(init_reboot_client_ip));
2808
2809        // Update request to have the test requested ip.
2810        req.options.push(DhcpOption::RequestedIpAddress(init_reboot_client_ip));
2811
2812        let server_id = server.params.server_ips.first().unwrap();
2813        let router = get_router(&server).expect("failed to get router");
2814        let dns_server = get_dns_server(&server).expect("failed to get dns server");
2815        assert_matches::assert_matches!(
2816            server.records.insert(
2817                ClientIdentifier::from(&req),
2818                LeaseRecord::new(
2819                    Some(init_reboot_client_ip),
2820                    vec![
2821                        DhcpOption::ServerIdentifier(*server_id),
2822                        DhcpOption::IpAddressLeaseTime(server.params.lease_length.default_seconds),
2823                        DhcpOption::RenewalTimeValue(
2824                            server.params.lease_length.default_seconds / 2
2825                        ),
2826                        DhcpOption::RebindingTimeValue(
2827                            (server.params.lease_length.default_seconds * 3) / 4,
2828                        ),
2829                        DhcpOption::SubnetMask(DEFAULT_PREFIX_LENGTH),
2830                        DhcpOption::Router(router),
2831                        DhcpOption::DomainNameServer(dns_server),
2832                    ],
2833                    time_source.now(),
2834                    u32::MAX,
2835                )
2836                .expect("failed to create lease record"),
2837            ),
2838            None
2839        );
2840
2841        let mut expected_ack = new_test_ack(&req, &server);
2842        expected_ack.yiaddr = init_reboot_client_ip;
2843
2844        let expected_response = if broadcast {
2845            Ok(ServerAction::SendResponse(expected_ack, ResponseTarget::Broadcast))
2846        } else {
2847            Ok(ServerAction::SendResponse(
2848                expected_ack,
2849                ResponseTarget::Unicast(init_reboot_client_ip, Some(req.chaddr)),
2850            ))
2851        };
2852        assert_eq!(server.dispatch(req), expected_response,);
2853    }
2854
2855    #[test]
2856    fn dispatch_with_init_boot_request_client_on_wrong_subnet_returns_nak() {
2857        let mut server = new_test_minimal_server();
2858        let mut req = new_test_request();
2859
2860        // Update request to have requested ip not on same subnet as server.
2861        req.options.push(DhcpOption::RequestedIpAddress(random_ipv4_generator()));
2862
2863        // The returned nak should be from this recipient server.
2864        let expected_nak = new_test_nak(&req, &server, NakReason::DifferentSubnets);
2865        assert_eq!(
2866            server.dispatch(req),
2867            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
2868        );
2869    }
2870
2871    #[test]
2872    fn dispatch_with_init_boot_request_with_giaddr_set_returns_nak_with_broadcast_bit_set() {
2873        let mut server = new_test_minimal_server();
2874        let mut req = new_test_request();
2875        req.giaddr = random_ipv4_generator();
2876
2877        // Update request to have requested ip not on same subnet as server,
2878        // to ensure we get a nak.
2879        req.options.push(DhcpOption::RequestedIpAddress(random_ipv4_generator()));
2880
2881        let response = server.dispatch(req).unwrap();
2882
2883        assert!(extract_message(response).bdcast_flag);
2884    }
2885
2886    #[test]
2887    fn dispatch_with_init_boot_request_unknown_client_mac_returns_error() {
2888        let mut server = new_test_minimal_server();
2889        let mut req = new_test_request();
2890
2891        let client_id = ClientIdentifier::from(&req);
2892
2893        // Update requested ip and server ip to be on the same subnet.
2894        req.options.push(DhcpOption::RequestedIpAddress(std_ip_v4!("192.165.30.45")));
2895        server.params.server_ips = vec![std_ip_v4!("192.165.30.1")];
2896
2897        assert_eq!(server.dispatch(req), Err(ServerError::UnknownClientId(client_id)));
2898    }
2899
2900    #[test]
2901    fn dispatch_with_init_boot_request_mismatched_requested_addr_returns_nak() {
2902        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2903        let mut req = new_test_request();
2904
2905        // Update requested ip and server ip to be on the same subnet.
2906        let init_reboot_client_ip = std_ip_v4!("192.165.25.4");
2907        req.options.push(DhcpOption::RequestedIpAddress(init_reboot_client_ip));
2908        server.params.server_ips = vec![std_ip_v4!("192.165.25.1")];
2909
2910        let server_cached_ip = std_ip_v4!("192.165.25.10");
2911        assert!(server.pool.allocated.insert(server_cached_ip));
2912        assert_matches::assert_matches!(
2913            server.records.insert(
2914                ClientIdentifier::from(&req),
2915                LeaseRecord::new(Some(server_cached_ip), Vec::new(), time_source.now(), u32::MAX,)
2916                    .expect("failed to create lease record"),
2917            ),
2918            None
2919        );
2920
2921        let expected_nak = new_test_nak(
2922            &req,
2923            &server,
2924            NakReason::ClientValidationFailure(ServerError::RequestedIpOfferIpMismatch(
2925                init_reboot_client_ip,
2926                server_cached_ip,
2927            )),
2928        );
2929        assert_eq!(
2930            server.dispatch(req),
2931            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
2932        );
2933    }
2934
2935    #[test]
2936    fn dispatch_with_init_boot_request_expired_client_binding_returns_nak() {
2937        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2938        let mut req = new_test_request();
2939
2940        let init_reboot_client_ip = std_ip_v4!("192.165.25.4");
2941        req.options.push(DhcpOption::RequestedIpAddress(init_reboot_client_ip));
2942        server.params.server_ips = vec![std_ip_v4!("192.165.25.1")];
2943
2944        assert!(server.pool.universe.insert(init_reboot_client_ip));
2945        assert!(server.pool.allocated.insert(init_reboot_client_ip));
2946        // Expire client binding to make it invalid.
2947        assert_matches::assert_matches!(
2948            server.records.insert(
2949                ClientIdentifier::from(&req),
2950                LeaseRecord::new(
2951                    Some(init_reboot_client_ip),
2952                    Vec::new(),
2953                    time_source.now(),
2954                    u32::MIN,
2955                )
2956                .expect("failed to create lease record"),
2957            ),
2958            None
2959        );
2960
2961        let expected_nak = new_test_nak(
2962            &req,
2963            &server,
2964            NakReason::ClientValidationFailure(ServerError::ExpiredLeaseRecord),
2965        );
2966
2967        assert_eq!(
2968            server.dispatch(req),
2969            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
2970        );
2971    }
2972
2973    #[test]
2974    fn dispatch_with_init_boot_request_no_reserved_addr_returns_nak() {
2975        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2976        let mut req = new_test_request();
2977
2978        let init_reboot_client_ip = std_ip_v4!("192.165.25.4");
2979        req.options.push(DhcpOption::RequestedIpAddress(init_reboot_client_ip));
2980        server.params.server_ips = vec![std_ip_v4!("192.165.25.1")];
2981
2982        assert_matches::assert_matches!(
2983            server.records.insert(
2984                ClientIdentifier::from(&req),
2985                LeaseRecord::new(
2986                    Some(init_reboot_client_ip),
2987                    Vec::new(),
2988                    time_source.now(),
2989                    u32::MAX,
2990                )
2991                .expect("failed to create lease record"),
2992            ),
2993            None
2994        );
2995
2996        let expected_nak = new_test_nak(
2997            &req,
2998            &server,
2999            NakReason::ClientValidationFailure(ServerError::UnidentifiedRequestedIp(
3000                init_reboot_client_ip,
3001            )),
3002        );
3003
3004        assert_eq!(
3005            server.dispatch(req),
3006            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
3007        );
3008    }
3009
3010    #[test]
3011    fn dispatch_with_renewing_request_returns_correct_ack() {
3012        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3013        let mut req = new_test_request();
3014
3015        let bound_client_ip = random_ipv4_generator();
3016
3017        assert!(server.pool.allocated.insert(bound_client_ip));
3018        req.ciaddr = bound_client_ip;
3019
3020        let server_id = server.params.server_ips.first().unwrap();
3021        let router = get_router(&server).expect("failed to get router");
3022        let dns_server = get_dns_server(&server).expect("failed to get dns server");
3023        assert_matches::assert_matches!(
3024            server.records.insert(
3025                ClientIdentifier::from(&req),
3026                LeaseRecord::new(
3027                    Some(bound_client_ip),
3028                    vec![
3029                        DhcpOption::ServerIdentifier(*server_id),
3030                        DhcpOption::IpAddressLeaseTime(server.params.lease_length.default_seconds),
3031                        DhcpOption::RenewalTimeValue(
3032                            server.params.lease_length.default_seconds / 2
3033                        ),
3034                        DhcpOption::RebindingTimeValue(
3035                            (server.params.lease_length.default_seconds * 3) / 4,
3036                        ),
3037                        DhcpOption::SubnetMask(DEFAULT_PREFIX_LENGTH),
3038                        DhcpOption::Router(router),
3039                        DhcpOption::DomainNameServer(dns_server),
3040                    ],
3041                    time_source.now(),
3042                    u32::MAX,
3043                )
3044                .expect("failed to create lease record"),
3045            ),
3046            None
3047        );
3048
3049        let mut expected_ack = new_test_ack(&req, &server);
3050        expected_ack.yiaddr = bound_client_ip;
3051        expected_ack.ciaddr = bound_client_ip;
3052
3053        let expected_dest = req.ciaddr;
3054
3055        assert_eq!(
3056            server.dispatch(req),
3057            Ok(ServerAction::SendResponse(
3058                expected_ack,
3059                ResponseTarget::Unicast(expected_dest, None)
3060            ))
3061        );
3062    }
3063
3064    #[test]
3065    fn dispatch_with_renewing_request_unknown_client_mac_returns_nak() {
3066        let mut server = new_test_minimal_server();
3067        let mut req = new_test_request();
3068
3069        let bound_client_ip = random_ipv4_generator();
3070        let client_id = ClientIdentifier::from(&req);
3071
3072        req.ciaddr = bound_client_ip;
3073
3074        let expected_nak = new_test_nak(
3075            &req,
3076            &server,
3077            NakReason::ClientValidationFailure(ServerError::UnknownClientId(client_id)),
3078        );
3079        assert_eq!(
3080            server.dispatch(req),
3081            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
3082        );
3083    }
3084
3085    #[test]
3086    fn dispatch_with_renewing_request_mismatched_requested_addr_returns_nak() {
3087        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3088        let mut req = new_test_request();
3089
3090        let client_renewal_ip = random_ipv4_generator();
3091        let bound_client_ip = random_ipv4_generator();
3092
3093        assert!(server.pool.allocated.insert(bound_client_ip));
3094        req.ciaddr = client_renewal_ip;
3095
3096        assert_matches::assert_matches!(
3097            server.records.insert(
3098                ClientIdentifier::from(&req),
3099                LeaseRecord::new(Some(bound_client_ip), Vec::new(), time_source.now(), u32::MAX)
3100                    .expect("failed to create lease record"),
3101            ),
3102            None
3103        );
3104
3105        let expected_nak = new_test_nak(
3106            &req,
3107            &server,
3108            NakReason::ClientValidationFailure(ServerError::RequestedIpOfferIpMismatch(
3109                client_renewal_ip,
3110                bound_client_ip,
3111            )),
3112        );
3113        assert_eq!(
3114            server.dispatch(req),
3115            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
3116        );
3117    }
3118
3119    #[test]
3120    fn dispatch_with_renewing_request_expired_client_binding_returns_nak() {
3121        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3122        let mut req = new_test_request();
3123
3124        let bound_client_ip = random_ipv4_generator();
3125
3126        assert!(server.pool.universe.insert(bound_client_ip));
3127        assert!(server.pool.allocated.insert(bound_client_ip));
3128        req.ciaddr = bound_client_ip;
3129
3130        assert_matches::assert_matches!(
3131            server.records.insert(
3132                ClientIdentifier::from(&req),
3133                LeaseRecord::new(Some(bound_client_ip), Vec::new(), time_source.now(), u32::MIN)
3134                    .expect("failed to create lease record"),
3135            ),
3136            None
3137        );
3138
3139        let expected_nak = new_test_nak(
3140            &req,
3141            &server,
3142            NakReason::ClientValidationFailure(ServerError::ExpiredLeaseRecord),
3143        );
3144        assert_eq!(
3145            server.dispatch(req),
3146            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
3147        );
3148    }
3149
3150    #[test]
3151    fn dispatch_with_renewing_request_no_reserved_addr_returns_nak() {
3152        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3153        let mut req = new_test_request();
3154
3155        let bound_client_ip = random_ipv4_generator();
3156        req.ciaddr = bound_client_ip;
3157
3158        assert_matches::assert_matches!(
3159            server.records.insert(
3160                ClientIdentifier::from(&req),
3161                LeaseRecord::new(Some(bound_client_ip), Vec::new(), time_source.now(), u32::MAX)
3162                    .expect("failed to create lease record"),
3163            ),
3164            None
3165        );
3166
3167        let expected_nak = new_test_nak(
3168            &req,
3169            &server,
3170            NakReason::ClientValidationFailure(ServerError::UnidentifiedRequestedIp(
3171                bound_client_ip,
3172            )),
3173        );
3174        assert_eq!(
3175            server.dispatch(req),
3176            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
3177        );
3178    }
3179
3180    #[test]
3181    fn dispatch_with_unknown_client_state_returns_error() {
3182        let mut server = new_test_minimal_server();
3183
3184        let req = new_test_request();
3185
3186        assert_eq!(server.dispatch(req), Err(ServerError::UnknownClientStateDuringRequest));
3187    }
3188
3189    #[test]
3190    fn get_client_state_with_selecting_returns_selecting() {
3191        let mut req = new_test_request();
3192
3193        // Selecting state request must have server id and requested ip populated.
3194        req.options.push(DhcpOption::ServerIdentifier(random_ipv4_generator()));
3195        req.options.push(DhcpOption::RequestedIpAddress(random_ipv4_generator()));
3196
3197        assert_eq!(get_client_state(&req), Ok(ClientState::Selecting));
3198    }
3199
3200    #[test]
3201    fn get_client_state_with_initreboot_returns_initreboot() {
3202        let mut req = new_test_request();
3203
3204        // Init reboot state request must have requested ip populated.
3205        req.options.push(DhcpOption::RequestedIpAddress(random_ipv4_generator()));
3206
3207        assert_eq!(get_client_state(&req), Ok(ClientState::InitReboot));
3208    }
3209
3210    #[test]
3211    fn get_client_state_with_renewing_returns_renewing() {
3212        let mut req = new_test_request();
3213
3214        // Renewing state request must have ciaddr populated.
3215        req.ciaddr = random_ipv4_generator();
3216
3217        assert_eq!(get_client_state(&req), Ok(ClientState::Renewing));
3218    }
3219
3220    #[test]
3221    fn get_client_state_with_unknown_returns_unknown() {
3222        let msg = new_test_request();
3223
3224        assert_eq!(get_client_state(&msg), Err(()));
3225    }
3226
3227    #[test]
3228    fn dispatch_with_client_msg_missing_message_type_option_returns_error() {
3229        let mut server = new_test_minimal_server();
3230        let mut msg = new_test_request();
3231        msg.options.clear();
3232
3233        assert_eq!(
3234            server.dispatch(msg),
3235            Err(ServerError::ClientMessageError(ProtocolError::MissingOption(
3236                OptionCode::DhcpMessageType
3237            )))
3238        );
3239    }
3240
3241    #[test]
3242    fn release_expired_leases_with_none_expired_releases_none() {
3243        let (mut server, mut time_source) = new_test_minimal_server_with_time_source();
3244        server.pool.universe.clear();
3245
3246        // Insert client 1 bindings.
3247        let client_1_ip = random_ipv4_generator();
3248        let client_1_id = ClientIdentifier::from(random_mac_generator());
3249        let client_opts = [DhcpOption::IpAddressLeaseTime(u32::MAX)];
3250        assert!(server.pool.universe.insert(client_1_ip));
3251        server
3252            .store_client_record(client_1_ip, client_1_id.clone(), &client_opts)
3253            .expect("failed to store client record");
3254
3255        // Insert client 2 bindings.
3256        let client_2_ip = random_ipv4_generator();
3257        let client_2_id = ClientIdentifier::from(random_mac_generator());
3258        assert!(server.pool.universe.insert(client_2_ip));
3259        server
3260            .store_client_record(client_2_ip, client_2_id.clone(), &client_opts)
3261            .expect("failed to store client record");
3262
3263        // Insert client 3 bindings.
3264        let client_3_ip = random_ipv4_generator();
3265        let client_3_id = ClientIdentifier::from(random_mac_generator());
3266        assert!(server.pool.universe.insert(client_3_ip));
3267        server
3268            .store_client_record(client_3_ip, client_3_id.clone(), &client_opts)
3269            .expect("failed to store client record");
3270
3271        time_source.move_forward(Duration::from_secs(1));
3272        server.release_expired_leases().expect("failed to release expired leases");
3273
3274        let client_ips: BTreeSet<_> = [client_1_ip, client_2_ip, client_3_ip].into();
3275        assert_matches::assert_matches!(server.records.get(&client_1_id), Some(LeaseRecord {current: Some(ip), previous: None, ..}) if *ip == client_1_ip);
3276        assert_matches::assert_matches!(server.records.get(&client_2_id), Some(LeaseRecord {current: Some(ip), previous: None, ..}) if *ip == client_2_ip);
3277        assert_matches::assert_matches!(server.records.get(&client_3_id), Some(LeaseRecord {current: Some(ip), previous: None, ..}) if *ip == client_3_ip);
3278        let available: Vec<_> = server.pool.available().collect();
3279        assert!(available.is_empty(), "{:?}", available);
3280        assert_eq!(server.pool.allocated, client_ips);
3281        assert_matches::assert_matches!(
3282            server.store.expect("missing store").actions().as_slice(),
3283            [
3284                DataStoreAction::StoreClientRecord { client_id: id_1, record: LeaseRecord { current: Some(ip1), previous: None, ..}, .. },
3285                DataStoreAction::StoreClientRecord { client_id: id_2, record: LeaseRecord { current: Some(ip2), previous: None, ..},.. },
3286                DataStoreAction::StoreClientRecord { client_id: id_3, record: LeaseRecord { current: Some(ip3), previous: None, ..},.. },
3287            ] if *id_1 == client_1_id && *id_2 == client_2_id && *id_3 == client_3_id &&
3288                *ip1 == client_1_ip && *ip2 == client_2_ip && *ip3 == client_3_ip
3289        );
3290    }
3291
3292    #[test]
3293    fn release_expired_leases_with_all_expired_releases_all() {
3294        let (mut server, mut time_source) = new_test_minimal_server_with_time_source();
3295        server.pool.universe.clear();
3296
3297        let client_1_ip = random_ipv4_generator();
3298        assert!(server.pool.universe.insert(client_1_ip));
3299        let client_1_id = ClientIdentifier::from(random_mac_generator());
3300        server
3301            .store_client_record(
3302                client_1_ip,
3303                client_1_id.clone(),
3304                &[DhcpOption::IpAddressLeaseTime(0)],
3305            )
3306            .expect("failed to store client record");
3307
3308        let client_2_ip = random_ipv4_generator();
3309        assert!(server.pool.universe.insert(client_2_ip));
3310        let client_2_id = ClientIdentifier::from(random_mac_generator());
3311        server
3312            .store_client_record(
3313                client_2_ip,
3314                client_2_id.clone(),
3315                &[DhcpOption::IpAddressLeaseTime(0)],
3316            )
3317            .expect("failed to store client record");
3318
3319        let client_3_ip = random_ipv4_generator();
3320        assert!(server.pool.universe.insert(client_3_ip));
3321        let client_3_id = ClientIdentifier::from(random_mac_generator());
3322        server
3323            .store_client_record(
3324                client_3_ip,
3325                client_3_id.clone(),
3326                &[DhcpOption::IpAddressLeaseTime(0)],
3327            )
3328            .expect("failed to store client record");
3329
3330        time_source.move_forward(Duration::from_secs(1));
3331        server.release_expired_leases().expect("failed to release expired leases");
3332
3333        assert_eq!(server.records.len(), 3);
3334        assert_matches::assert_matches!(server.records.get(&client_1_id), Some(LeaseRecord {current: None, previous: Some(ip), ..}) if *ip == client_1_ip);
3335        assert_matches::assert_matches!(server.records.get(&client_2_id), Some(LeaseRecord {current: None, previous: Some(ip), ..}) if *ip == client_2_ip);
3336        assert_matches::assert_matches!(server.records.get(&client_3_id), Some(LeaseRecord {current: None, previous: Some(ip), ..}) if *ip == client_3_ip);
3337        assert_eq!(
3338            server.pool.available().collect::<HashSet<_>>(),
3339            [client_1_ip, client_2_ip, client_3_ip].into(),
3340        );
3341        assert!(server.pool.allocated.is_empty(), "{:?}", server.pool.allocated);
3342        // Delete actions occur in non-deterministic (HashMap iteration) order, so we must not
3343        // assert on the ordering of the deleted ids.
3344        assert_matches::assert_matches!(
3345            &server.store.expect("missing store").actions().as_slice()[..],
3346            [
3347                DataStoreAction::StoreClientRecord { client_id: id_1, record: LeaseRecord { current: Some(ip1), previous: None, ..}, .. },
3348                DataStoreAction::StoreClientRecord { client_id: id_2, record: LeaseRecord { current: Some(ip2), previous: None, ..},.. },
3349                DataStoreAction::StoreClientRecord { client_id: id_3, record: LeaseRecord { current: Some(ip3), previous: None, ..},.. },
3350                DataStoreAction::StoreClientRecord { client_id: update_id_1, record: LeaseRecord { current: None, previous: Some(update_ip_1), ..}, .. },
3351                DataStoreAction::StoreClientRecord { client_id: update_id_2, record: LeaseRecord { current: None, previous: Some(update_ip_2), ..}, .. },
3352                DataStoreAction::StoreClientRecord { client_id: update_id_3, record: LeaseRecord { current: None, previous: Some(update_ip_3), ..}, .. },
3353            ] if *id_1 == client_1_id && *id_2 == client_2_id && *id_3 == client_3_id &&
3354                *ip1 == client_1_ip && *ip2 == client_2_ip && *ip3 == client_3_ip &&
3355                [update_id_1, update_id_2, update_id_3].iter().all(|id| {
3356                    [&client_1_id, &client_2_id, &client_3_id].contains(id)
3357                }) &&
3358                [update_ip_1, update_ip_2, update_ip_3].iter().all(|ip| {
3359                    [&client_1_ip, &client_2_ip, &client_3_ip].contains(ip)
3360                })
3361        );
3362    }
3363
3364    #[test]
3365    fn release_expired_leases_with_some_expired_releases_expired() {
3366        let (mut server, mut time_source) = new_test_minimal_server_with_time_source();
3367        server.pool.universe.clear();
3368
3369        let client_1_ip = random_ipv4_generator();
3370        assert!(server.pool.universe.insert(client_1_ip));
3371        let client_1_id = ClientIdentifier::from(random_mac_generator());
3372        server
3373            .store_client_record(
3374                client_1_ip,
3375                client_1_id.clone(),
3376                &[DhcpOption::IpAddressLeaseTime(u32::MAX)],
3377            )
3378            .expect("failed to store client record");
3379
3380        let client_2_ip = random_ipv4_generator();
3381        assert!(server.pool.universe.insert(client_2_ip));
3382        let client_2_id = ClientIdentifier::from(random_mac_generator());
3383        server
3384            .store_client_record(
3385                client_2_ip,
3386                client_2_id.clone(),
3387                &[DhcpOption::IpAddressLeaseTime(0)],
3388            )
3389            .expect("failed to store client record");
3390
3391        let client_3_ip = random_ipv4_generator();
3392        assert!(server.pool.universe.insert(client_3_ip));
3393        let client_3_id = ClientIdentifier::from(random_mac_generator());
3394        server
3395            .store_client_record(
3396                client_3_ip,
3397                client_3_id.clone(),
3398                &[DhcpOption::IpAddressLeaseTime(u32::MAX)],
3399            )
3400            .expect("failed to store client record");
3401
3402        time_source.move_forward(Duration::from_secs(1));
3403        server.release_expired_leases().expect("failed to release expired leases");
3404
3405        let client_ips: BTreeSet<_> = [client_1_ip, client_3_ip].into();
3406        assert_matches::assert_matches!(server.records.get(&client_1_id), Some(LeaseRecord {current: Some(ip), previous: None, ..}) if *ip == client_1_ip);
3407        assert_matches::assert_matches!(server.records.get(&client_2_id), Some(LeaseRecord {current: None, previous: Some(ip), ..}) if *ip == client_2_ip);
3408        assert_matches::assert_matches!(server.records.get(&client_3_id), Some(LeaseRecord {current: Some(ip), previous: None, ..}) if *ip == client_3_ip);
3409        assert_eq!(server.pool.available().collect::<Vec<_>>(), vec![client_2_ip]);
3410        assert_eq!(server.pool.allocated, client_ips);
3411        assert_matches::assert_matches!(
3412            server.store.expect("missing store").actions().as_slice(),
3413            [
3414                DataStoreAction::StoreClientRecord { client_id: id_1, record: LeaseRecord { current: Some(ip1), previous: None, ..}, .. },
3415                DataStoreAction::StoreClientRecord { client_id: id_2, record: LeaseRecord { current: Some(ip2), previous: None, ..},.. },
3416                DataStoreAction::StoreClientRecord { client_id: id_3, record: LeaseRecord { current: Some(ip3), previous: None, ..},.. },
3417                DataStoreAction::StoreClientRecord { client_id: update_id_1, record: LeaseRecord { current: None, previous: Some(update_ip_1), ..}, ..},
3418            ] if *id_1 == client_1_id && *id_2 == client_2_id && *id_3 == client_3_id && *update_id_1 == client_2_id &&
3419                *ip1 == client_1_ip && *ip2 == client_2_ip && *ip3 == client_3_ip && *update_ip_1 == client_2_ip
3420        );
3421    }
3422
3423    #[test]
3424    fn dispatch_with_known_release_updates_address_pool_retains_client_record() {
3425        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3426        let mut release = new_test_release();
3427
3428        let release_ip = random_ipv4_generator();
3429        let client_id = ClientIdentifier::from(&release);
3430
3431        assert!(server.pool.universe.insert(release_ip));
3432        assert!(server.pool.allocated.insert(release_ip));
3433        release.ciaddr = release_ip;
3434
3435        let dns = random_ipv4_generator();
3436        let opts = vec![DhcpOption::DomainNameServer([dns].into())];
3437        let test_client_record = |client_addr: Option<Ipv4Addr>, opts: Vec<DhcpOption>| {
3438            LeaseRecord::new(client_addr, opts, time_source.now(), u32::MAX).unwrap()
3439        };
3440
3441        assert_matches::assert_matches!(
3442            server
3443                .records
3444                .insert(client_id.clone(), test_client_record(Some(release_ip), opts.clone())),
3445            None
3446        );
3447
3448        assert_eq!(server.dispatch(release), Ok(ServerAction::AddressRelease(release_ip)));
3449        assert_matches::assert_matches!(
3450            server.store.expect("missing store").actions().as_slice(),
3451            [
3452                DataStoreAction::StoreClientRecord { client_id: id, record: LeaseRecord {current: None, previous: Some(ip), options, ..}}
3453            ] if *id == client_id  &&  *ip == release_ip && *options == opts
3454        );
3455        assert!(!server.pool.addr_is_allocated(release_ip), "addr marked allocated");
3456        assert!(server.pool.addr_is_available(release_ip), "addr not marked available");
3457        assert!(server.records.contains_key(&client_id), "client record not retained");
3458        assert_matches::assert_matches!(
3459            server.records.get(&client_id),
3460            Some(LeaseRecord {current: None, previous: Some(ip), options, lease_length_seconds, ..})
3461               if *ip == release_ip && *options == opts && *lease_length_seconds == u32::MAX
3462        );
3463    }
3464
3465    #[test]
3466    fn dispatch_with_unknown_release_maintains_server_state_returns_unknown_mac_error() {
3467        let mut server = new_test_minimal_server();
3468        let mut release = new_test_release();
3469
3470        let release_ip = random_ipv4_generator();
3471        let client_id = ClientIdentifier::from(&release);
3472
3473        assert!(server.pool.allocated.insert(release_ip));
3474        release.ciaddr = release_ip;
3475
3476        assert_eq!(server.dispatch(release), Err(ServerError::UnknownClientId(client_id)));
3477
3478        assert!(server.pool.addr_is_allocated(release_ip), "addr not marked allocated");
3479        assert!(!server.pool.addr_is_available(release_ip), "addr still marked available");
3480    }
3481
3482    #[test]
3483    fn dispatch_invalid_dhcp_release() {
3484        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3485        let mut release = new_test_release();
3486
3487        let allocated_ip = std_ip_v4!("1.2.3.4");
3488        let bogus_ip = std_ip_v4!("5.6.7.8");
3489        let client_id = ClientIdentifier::from(&release);
3490
3491        assert!(server.pool.allocated.insert(allocated_ip));
3492        assert!(server.pool.universe.insert(allocated_ip));
3493        assert_matches!(
3494            server.records.insert(
3495                client_id.clone(),
3496                LeaseRecord::new(Some(allocated_ip), vec![], time_source.now(), u32::MAX).unwrap()
3497            ),
3498            None
3499        );
3500
3501        release.ciaddr = bogus_ip;
3502
3503        assert_eq!(
3504            server.dispatch(release),
3505            Err(ServerError::InvalidReleaseAddr { client: client_id, addr: bogus_ip })
3506        );
3507
3508        assert!(server.pool.addr_is_allocated(allocated_ip), "addr not marked allocated");
3509        assert!(!server.pool.addr_is_available(allocated_ip), "addr still marked available");
3510    }
3511
3512    #[test]
3513    fn dispatch_dhcp_release_twice() {
3514        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3515        let mac = random_mac_generator();
3516        let make_test_release_with_ciaddr =
3517            move |ciaddr| Message { ciaddr, chaddr: mac, ..new_test_release() };
3518
3519        let allocated_ip = std_ip_v4!("1.2.3.4");
3520        let client_id = ClientIdentifier::from(mac);
3521
3522        assert!(server.pool.allocated.insert(allocated_ip));
3523        assert!(server.pool.universe.insert(allocated_ip));
3524        assert_matches!(
3525            server.records.insert(
3526                client_id.clone(),
3527                LeaseRecord::new(Some(allocated_ip), vec![], time_source.now(), u32::MAX).unwrap()
3528            ),
3529            None
3530        );
3531
3532        assert_eq!(
3533            server.dispatch(make_test_release_with_ciaddr(allocated_ip)),
3534            Ok(ServerAction::AddressRelease(allocated_ip))
3535        );
3536        assert!(!server.pool.addr_is_allocated(allocated_ip), "addr is still allocated");
3537        assert!(server.pool.addr_is_available(allocated_ip), "addr not available");
3538
3539        let mut store = server.store.take().expect("missing store");
3540        let actions = store.actions();
3541        let (id, ip) = assert_matches!(
3542            actions.as_slice(),
3543            [
3544                DataStoreAction::StoreClientRecord {
3545                    client_id: id,
3546                    record: LeaseRecord {
3547                        current: None,
3548                        previous: Some(ip),
3549                        ..
3550                    }
3551                },
3552            ] => (id, ip)
3553        );
3554        assert_eq!(id, &client_id);
3555        assert_eq!(ip, &allocated_ip);
3556
3557        assert_eq!(
3558            server.dispatch(make_test_release_with_ciaddr(allocated_ip)),
3559            Err(ServerError::InvalidReleaseAddr { client: client_id, addr: allocated_ip })
3560        );
3561        assert!(!server.pool.addr_is_allocated(allocated_ip), "addr is still allocated");
3562        assert!(server.pool.addr_is_available(allocated_ip), "addr not available");
3563    }
3564
3565    #[test]
3566    fn dispatch_with_inform_returns_correct_ack() {
3567        let mut server = new_test_minimal_server();
3568        let mut inform = new_test_inform();
3569
3570        let inform_client_ip = random_ipv4_generator();
3571
3572        inform.ciaddr = inform_client_ip;
3573
3574        let mut expected_ack = new_test_inform_ack(&inform, &server);
3575        expected_ack.ciaddr = inform_client_ip;
3576
3577        let expected_dest = inform.ciaddr;
3578
3579        assert_eq!(
3580            server.dispatch(inform),
3581            Ok(ServerAction::SendResponse(
3582                expected_ack,
3583                ResponseTarget::Unicast(expected_dest, None)
3584            ))
3585        );
3586    }
3587
3588    #[test_case(
3589        [OptionCode::DomainNameServer, OptionCode::SubnetMask, OptionCode::Router].into(),
3590        [OptionCode::DomainNameServer, OptionCode::SubnetMask, OptionCode::Router].into();
3591        "Valid order should be unmodified"
3592    )]
3593    #[test_case(
3594        [OptionCode::Router, OptionCode::SubnetMask].into(),
3595        [OptionCode::SubnetMask, OptionCode::Router].into();
3596        "SubnetMask should be moved to before Router"
3597    )]
3598    #[test_case(
3599        [OptionCode::Router, OptionCode::DomainNameServer, OptionCode::SubnetMask].into(),
3600        [OptionCode::SubnetMask, OptionCode::Router, OptionCode::DomainNameServer].into();
3601        "When SubnetMask is moved, Router should maintain its relative position"
3602    )]
3603    fn enforce_subnet_option_order(
3604        req_order: AtLeast<1, AtMostBytes<{ dhcp_protocol::U8_MAX_AS_USIZE }, Vec<OptionCode>>>,
3605        expected_order: AtLeast<
3606            1,
3607            AtMostBytes<{ dhcp_protocol::U8_MAX_AS_USIZE }, Vec<OptionCode>>,
3608        >,
3609    ) {
3610        // According to spec, subnet mask must be provided before the Router.
3611        // This test creates various PRLs and expects the server to move the
3612        // subnet mask when necessary.
3613        let mut server = new_test_minimal_server();
3614        let inform = new_client_message_with_options([
3615            DhcpOption::DhcpMessageType(MessageType::DHCPINFORM),
3616            DhcpOption::ParameterRequestList(req_order),
3617        ]);
3618
3619        let server_action = server.dispatch(inform);
3620        let ack = assert_matches::assert_matches!(
3621            server_action, Ok(ServerAction::SendResponse(ack,_)) => ack
3622        );
3623        let ack_order: Vec<_> = ack.options.iter().map(|option| option.code()).collect();
3624        // First two options are always MessageType and ServerIdentifier.
3625        // Both of which don't correspond with the ParameterRequestList.
3626        let expected_order: Vec<_> = [OptionCode::DhcpMessageType, OptionCode::ServerIdentifier]
3627            .into_iter()
3628            .chain(expected_order)
3629            .collect();
3630        assert_eq!(ack_order, expected_order)
3631    }
3632
3633    #[test]
3634    fn dispatch_with_decline_for_allocated_addr_returns_ok() {
3635        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3636        let mut decline = new_test_decline(&server);
3637
3638        let declined_ip = random_ipv4_generator();
3639        let client_id = ClientIdentifier::from(&decline);
3640
3641        decline.options.push(DhcpOption::RequestedIpAddress(declined_ip));
3642
3643        assert!(server.pool.allocated.insert(declined_ip));
3644        assert!(server.pool.universe.insert(declined_ip));
3645
3646        assert_matches::assert_matches!(
3647            server.records.insert(
3648                client_id.clone(),
3649                LeaseRecord::new(Some(declined_ip), Vec::new(), time_source.now(), u32::MAX)
3650                    .expect("failed to create lease record"),
3651            ),
3652            None
3653        );
3654
3655        assert_eq!(server.dispatch(decline), Ok(ServerAction::AddressDecline(declined_ip)));
3656        assert!(!server.pool.addr_is_available(declined_ip), "addr still marked available");
3657        assert!(server.pool.addr_is_allocated(declined_ip), "addr not marked allocated");
3658        assert!(!server.records.contains_key(&client_id), "client record incorrectly retained");
3659        assert_matches::assert_matches!(
3660            server.store.expect("missing store").actions().as_slice(),
3661            [DataStoreAction::Delete { client_id: id }] if *id == client_id
3662        );
3663    }
3664
3665    #[test]
3666    fn dispatch_with_decline_for_available_addr_returns_ok() {
3667        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3668        let mut decline = new_test_decline(&server);
3669
3670        let declined_ip = random_ipv4_generator();
3671        let client_id = ClientIdentifier::from(&decline);
3672
3673        decline.options.push(DhcpOption::RequestedIpAddress(declined_ip));
3674        assert_matches::assert_matches!(
3675            server.records.insert(
3676                client_id.clone(),
3677                LeaseRecord::new(Some(declined_ip), Vec::new(), time_source.now(), u32::MAX)
3678                    .expect("failed to create lease record"),
3679            ),
3680            None
3681        );
3682        assert!(server.pool.universe.insert(declined_ip));
3683
3684        assert_eq!(server.dispatch(decline), Ok(ServerAction::AddressDecline(declined_ip)));
3685        assert!(!server.pool.addr_is_available(declined_ip), "addr still marked available");
3686        assert!(server.pool.addr_is_allocated(declined_ip), "addr not marked allocated");
3687        assert!(!server.records.contains_key(&client_id), "client record incorrectly retained");
3688        assert_matches::assert_matches!(
3689            server.store.expect("missing store").actions().as_slice(),
3690            [DataStoreAction::Delete { client_id: id }] if *id == client_id
3691        );
3692    }
3693
3694    #[test]
3695    fn dispatch_with_decline_for_mismatched_addr_returns_err() {
3696        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3697        let mut decline = new_test_decline(&server);
3698
3699        let declined_ip = random_ipv4_generator();
3700        let client_id = ClientIdentifier::from(&decline);
3701
3702        decline.options.push(DhcpOption::RequestedIpAddress(declined_ip));
3703
3704        let client_ip_according_to_server = random_ipv4_generator();
3705        assert!(server.pool.allocated.insert(client_ip_according_to_server));
3706        assert!(server.pool.universe.insert(declined_ip));
3707
3708        // Server contains client bindings which reflect a different address
3709        // than the one being declined.
3710        assert_matches::assert_matches!(
3711            server.records.insert(
3712                client_id.clone(),
3713                LeaseRecord::new(
3714                    Some(client_ip_according_to_server),
3715                    Vec::new(),
3716                    time_source.now(),
3717                    u32::MAX,
3718                )
3719                .expect("failed to create lease record"),
3720            ),
3721            None
3722        );
3723
3724        assert_eq!(
3725            server.dispatch(decline),
3726            Err(ServerError::DeclineIpMismatch {
3727                declined: Some(declined_ip),
3728                client: Some(client_ip_according_to_server)
3729            })
3730        );
3731        assert!(server.pool.addr_is_available(declined_ip), "addr not marked available");
3732        assert!(!server.pool.addr_is_allocated(declined_ip), "addr marked allocated");
3733        assert!(server.records.contains_key(&client_id), "client record deleted from records");
3734    }
3735
3736    #[test]
3737    fn dispatch_with_decline_for_expired_lease_returns_ok() {
3738        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3739        let mut decline = new_test_decline(&server);
3740
3741        let declined_ip = random_ipv4_generator();
3742        let client_id = ClientIdentifier::from(&decline);
3743
3744        decline.options.push(DhcpOption::RequestedIpAddress(declined_ip));
3745
3746        assert!(server.pool.universe.insert(declined_ip));
3747
3748        assert_matches::assert_matches!(
3749            server.records.insert(
3750                client_id.clone(),
3751                LeaseRecord::new(Some(declined_ip), Vec::new(), time_source.now(), u32::MIN)
3752                    .expect("failed to create lease record"),
3753            ),
3754            None
3755        );
3756
3757        assert_eq!(server.dispatch(decline), Ok(ServerAction::AddressDecline(declined_ip)));
3758        assert!(!server.pool.addr_is_available(declined_ip), "addr still marked available");
3759        assert!(server.pool.addr_is_allocated(declined_ip), "addr not marked allocated");
3760        assert!(!server.records.contains_key(&client_id), "client record incorrectly retained");
3761        assert_matches::assert_matches!(
3762            server.store.expect("failed to create ").actions().as_slice(),
3763            [DataStoreAction::Delete { client_id: id }] if *id == client_id
3764        );
3765    }
3766
3767    #[test]
3768    fn dispatch_with_decline_for_unknown_client_returns_err() {
3769        let mut server = new_test_minimal_server();
3770        let mut decline = new_test_decline(&server);
3771
3772        let declined_ip = random_ipv4_generator();
3773        let client_id = ClientIdentifier::from(&decline);
3774
3775        decline.options.push(DhcpOption::RequestedIpAddress(declined_ip));
3776
3777        assert!(server.pool.universe.insert(declined_ip));
3778
3779        assert_eq!(
3780            server.dispatch(decline),
3781            Err(ServerError::DeclineFromUnrecognizedClient(client_id))
3782        );
3783        assert!(server.pool.addr_is_available(declined_ip), "addr not marked available");
3784        assert!(!server.pool.addr_is_allocated(declined_ip), "addr marked allocated");
3785    }
3786
3787    #[test]
3788    fn dispatch_with_decline_for_incorrect_server_returns_err() {
3789        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3790        server.params.server_ips = vec![random_ipv4_generator()];
3791
3792        let mut decline = new_client_message(MessageType::DHCPDECLINE);
3793        let server_id = random_ipv4_generator();
3794        decline.options.push(DhcpOption::ServerIdentifier(server_id));
3795
3796        let declined_ip = random_ipv4_generator();
3797        let client_id = ClientIdentifier::from(&decline);
3798
3799        decline.options.push(DhcpOption::RequestedIpAddress(declined_ip));
3800
3801        assert!(server.pool.allocated.insert(declined_ip));
3802        assert_matches::assert_matches!(
3803            server.records.insert(
3804                client_id.clone(),
3805                LeaseRecord::new(Some(declined_ip), Vec::new(), time_source.now(), u32::MAX)
3806                    .expect("failed to create lease record"),
3807            ),
3808            None
3809        );
3810
3811        assert_eq!(server.dispatch(decline), Err(ServerError::IncorrectDHCPServer(server_id)));
3812        assert!(!server.pool.addr_is_available(declined_ip), "addr marked available");
3813        assert!(server.pool.addr_is_allocated(declined_ip), "addr not marked allocated");
3814        assert!(server.records.contains_key(&client_id), "client record not retained");
3815    }
3816
3817    #[test]
3818    fn dispatch_with_decline_without_requested_addr_returns_err() {
3819        let mut server = new_test_minimal_server();
3820        let decline = new_test_decline(&server);
3821
3822        assert_eq!(server.dispatch(decline), Err(ServerError::NoRequestedAddrForDecline));
3823    }
3824
3825    #[test]
3826    fn client_requested_lease_time() {
3827        let mut disc = new_test_discover();
3828        let client_id = ClientIdentifier::from(&disc);
3829
3830        let client_requested_time: u32 = 20;
3831
3832        disc.options.push(DhcpOption::IpAddressLeaseTime(client_requested_time));
3833
3834        let mut server = new_test_minimal_server();
3835        assert!(server.pool.universe.insert(random_ipv4_generator()));
3836
3837        let response = server.dispatch(disc).unwrap();
3838        assert_eq!(
3839            extract_message(response)
3840                .options
3841                .iter()
3842                .filter_map(|opt| {
3843                    if let DhcpOption::IpAddressLeaseTime(v) = opt { Some(*v) } else { None }
3844                })
3845                .next()
3846                .unwrap(),
3847            client_requested_time as u32
3848        );
3849
3850        assert_eq!(
3851            server.records.get(&client_id).unwrap().lease_length_seconds,
3852            client_requested_time,
3853        );
3854        assert_matches::assert_matches!(
3855            server.store.expect("missing store").actions().as_slice(),
3856            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
3857        );
3858    }
3859
3860    #[test]
3861    fn client_requested_lease_time_greater_than_max() {
3862        let mut disc = new_test_discover();
3863        let client_id = ClientIdentifier::from(&disc);
3864
3865        let client_requested_time: u32 = 20;
3866        let server_max_lease_time: u32 = 10;
3867
3868        disc.options.push(DhcpOption::IpAddressLeaseTime(client_requested_time));
3869
3870        let mut server = new_test_minimal_server();
3871        assert!(server.pool.universe.insert(std_ip_v4!("195.168.1.45")));
3872        let ll = LeaseLength { default_seconds: 60 * 60 * 24, max_seconds: server_max_lease_time };
3873        server.params.lease_length = ll;
3874
3875        let response = server.dispatch(disc).unwrap();
3876        assert_eq!(
3877            extract_message(response)
3878                .options
3879                .iter()
3880                .filter_map(|opt| {
3881                    if let DhcpOption::IpAddressLeaseTime(v) = opt { Some(*v) } else { None }
3882                })
3883                .next()
3884                .unwrap(),
3885            server_max_lease_time
3886        );
3887
3888        assert_eq!(
3889            server.records.get(&client_id).unwrap().lease_length_seconds,
3890            server_max_lease_time,
3891        );
3892        assert_matches::assert_matches!(
3893            server.store.expect("missing store").actions().as_slice(),
3894            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
3895        );
3896    }
3897
3898    #[test]
3899    fn server_dispatcher_get_option_with_unset_option_returns_not_found() {
3900        let server = new_test_minimal_server();
3901        let result = server.dispatch_get_option(fidl_fuchsia_net_dhcp::OptionCode::SubnetMask);
3902        assert_eq!(result, Err(Status::NOT_FOUND));
3903    }
3904
3905    #[test]
3906    fn server_dispatcher_get_option_with_set_option_returns_option() {
3907        let mut server = new_test_minimal_server();
3908        let option = || fidl_fuchsia_net_dhcp::Option_::SubnetMask(fidl_ip_v4!("255.255.255.0"));
3909        assert_matches::assert_matches!(
3910            server.options_repo.insert(
3911                OptionCode::SubnetMask,
3912                DhcpOption::try_from_fidl(option())
3913                    .expect("failed to convert dhcp option from fidl")
3914            ),
3915            None
3916        );
3917        let result = server
3918            .dispatch_get_option(fidl_fuchsia_net_dhcp::OptionCode::SubnetMask)
3919            .expect("failed to get dhcp option");
3920        assert_eq!(result, option());
3921    }
3922
3923    #[test]
3924    fn server_dispatcher_get_parameter_returns_parameter() {
3925        let mut server = new_test_minimal_server();
3926        let addr = random_ipv4_generator();
3927        server.params.server_ips = vec![addr];
3928        let expected = fidl_fuchsia_net_dhcp::Parameter::IpAddrs(vec![addr.into_fidl()]);
3929        let result = server
3930            .dispatch_get_parameter(fidl_fuchsia_net_dhcp::ParameterName::IpAddrs)
3931            .expect("failed to get dhcp option");
3932        assert_eq!(result, expected);
3933    }
3934
3935    #[test]
3936    fn server_dispatcher_set_option_returns_unit() {
3937        let mut server = new_test_minimal_server();
3938        let option = || fidl_fuchsia_net_dhcp::Option_::SubnetMask(fidl_ip_v4!("255.255.255.0"));
3939        server.dispatch_set_option(option()).expect("failed to set dhcp option");
3940        let stored_option: DhcpOption =
3941            DhcpOption::try_from_fidl(option()).expect("failed to convert dhcp option from fidl");
3942        let code = stored_option.code();
3943        let result = server.options_repo.get(&code);
3944        assert_eq!(result, Some(&stored_option));
3945        assert_matches::assert_matches!(
3946            server.store.expect("missing store").actions().as_slice(),
3947            [
3948                DataStoreAction::StoreOptions { opts },
3949            ] if opts.contains(&stored_option)
3950        );
3951    }
3952
3953    #[test]
3954    fn server_dispatcher_set_option_saves_to_stash() {
3955        let prefix_length = DEFAULT_PREFIX_LENGTH;
3956        let fidl_mask =
3957            fidl_fuchsia_net_dhcp::Option_::SubnetMask(prefix_length.get_mask().into_ext());
3958        let params = default_server_params().expect("failed to get default serve parameters");
3959        let mut server: Server = super::Server {
3960            records: HashMap::new(),
3961            pool: AddressPool::new(params.managed_addrs.pool_range()),
3962            params,
3963            store: Some(ActionRecordingDataStore::new()),
3964            options_repo: HashMap::new(),
3965            time_source: TestSystemTime::with_current_time(),
3966        };
3967        server.dispatch_set_option(fidl_mask).expect("failed to set dhcp option");
3968        assert_matches::assert_matches!(
3969            server.store.expect("missing store").actions().as_slice(),
3970            [
3971                DataStoreAction::StoreOptions { opts },
3972            ] if *opts == vec![DhcpOption::SubnetMask(prefix_length)]
3973        );
3974    }
3975
3976    #[test]
3977    fn server_dispatcher_set_parameter_saves_to_stash() {
3978        let (default, max) = (42, 100);
3979        let fidl_lease =
3980            fidl_fuchsia_net_dhcp::Parameter::Lease(fidl_fuchsia_net_dhcp::LeaseLength {
3981                default: Some(default),
3982                max: Some(max),
3983                ..Default::default()
3984            });
3985        let mut server = new_test_minimal_server();
3986        server.dispatch_set_parameter(fidl_lease).expect("failed to set parameter");
3987        assert_matches::assert_matches!(
3988            server.store.expect("missing store").actions().next(),
3989            Some(DataStoreAction::StoreParameters {
3990                params: ServerParameters {
3991                    lease_length: LeaseLength { default_seconds: 42, max_seconds: 100 },
3992                    ..
3993                },
3994            })
3995        );
3996    }
3997
3998    #[test]
3999    fn server_dispatcher_set_parameter() {
4000        let mut server = new_test_minimal_server();
4001        let addr = random_ipv4_generator();
4002        let valid_parameter = || fidl_fuchsia_net_dhcp::Parameter::IpAddrs(vec![addr.into_fidl()]);
4003        let empty_lease_length =
4004            fidl_fuchsia_net_dhcp::Parameter::Lease(fidl_fuchsia_net_dhcp::LeaseLength {
4005                default: None,
4006                max: None,
4007                ..Default::default()
4008            });
4009        let bad_prefix_length =
4010            fidl_fuchsia_net_dhcp::Parameter::AddressPool(fidl_fuchsia_net_dhcp::AddressPool {
4011                prefix_length: Some(33),
4012                range_start: Some(fidl_ip_v4!("192.168.0.2")),
4013                range_stop: Some(fidl_ip_v4!("192.168.0.254")),
4014                ..Default::default()
4015            });
4016        let mac = random_mac_generator().bytes();
4017        let duplicated_static_assignment =
4018            fidl_fuchsia_net_dhcp::Parameter::StaticallyAssignedAddrs(vec![
4019                fidl_fuchsia_net_dhcp::StaticAssignment {
4020                    host: Some(fidl_fuchsia_net::MacAddress { octets: mac.clone() }),
4021                    assigned_addr: Some(random_ipv4_generator().into_fidl()),
4022                    ..Default::default()
4023                },
4024                fidl_fuchsia_net_dhcp::StaticAssignment {
4025                    host: Some(fidl_fuchsia_net::MacAddress { octets: mac.clone() }),
4026                    assigned_addr: Some(random_ipv4_generator().into_fidl()),
4027                    ..Default::default()
4028                },
4029            ]);
4030
4031        let () =
4032            server.dispatch_set_parameter(valid_parameter()).expect("failed to set dhcp parameter");
4033        assert_eq!(
4034            server
4035                .dispatch_get_parameter(fidl_fuchsia_net_dhcp::ParameterName::IpAddrs)
4036                .expect("failed to get dhcp parameter"),
4037            valid_parameter()
4038        );
4039        assert_eq!(
4040            server.dispatch_set_parameter(empty_lease_length),
4041            Err(zx::Status::INVALID_ARGS)
4042        );
4043        assert_eq!(server.dispatch_set_parameter(bad_prefix_length), Err(zx::Status::INVALID_ARGS));
4044        assert_eq!(
4045            server.dispatch_set_parameter(duplicated_static_assignment),
4046            Err(zx::Status::INVALID_ARGS)
4047        );
4048        assert_matches::assert_matches!(
4049            server.store.expect("missing store").actions().as_slice(),
4050            [DataStoreAction::StoreParameters { params }] if *params == server.params
4051        );
4052    }
4053
4054    #[test]
4055    fn server_dispatcher_list_options_returns_set_options() {
4056        let mut server = new_test_minimal_server();
4057        let mask = || {
4058            fidl_fuchsia_net_dhcp::Option_::SubnetMask(DEFAULT_PREFIX_LENGTH.get_mask().into_ext())
4059        };
4060        let hostname = || fidl_fuchsia_net_dhcp::Option_::HostName(String::from("testhostname"));
4061        assert_matches::assert_matches!(
4062            server.options_repo.insert(
4063                OptionCode::SubnetMask,
4064                DhcpOption::try_from_fidl(mask()).expect("failed to convert dhcp option from fidl")
4065            ),
4066            None
4067        );
4068        assert_matches::assert_matches!(
4069            server.options_repo.insert(
4070                OptionCode::HostName,
4071                DhcpOption::try_from_fidl(hostname())
4072                    .expect("failed to convert dhcp option from fidl")
4073            ),
4074            None
4075        );
4076        let result = server.dispatch_list_options().expect("failed to list dhcp options");
4077        assert_eq!(result.len(), server.options_repo.len());
4078        assert!(result.contains(&mask()));
4079        assert!(result.contains(&hostname()));
4080    }
4081
4082    #[test]
4083    fn server_dispatcher_list_parameters_returns_parameters() {
4084        let mut server = new_test_minimal_server();
4085        let addr = random_ipv4_generator();
4086        server.params.server_ips = vec![addr];
4087        let expected = fidl_fuchsia_net_dhcp::Parameter::IpAddrs(vec![addr.into_fidl()]);
4088        let result = server.dispatch_list_parameters().expect("failed to list dhcp options");
4089        let params_fields_ct = 7;
4090        assert_eq!(result.len(), params_fields_ct);
4091        assert!(result.contains(&expected));
4092    }
4093
4094    #[test]
4095    fn server_dispatcher_reset_options() {
4096        let mut server = new_test_minimal_server();
4097        let empty_map = HashMap::new();
4098        assert_ne!(empty_map, server.options_repo);
4099        server.dispatch_reset_options().expect("failed to reset options");
4100        assert_eq!(empty_map, server.options_repo);
4101        let stored_opts = server
4102            .store
4103            .as_mut()
4104            .expect("missing store")
4105            .load_options()
4106            .expect("failed to load options");
4107        assert_eq!(empty_map, stored_opts);
4108        assert_matches::assert_matches!(
4109            server.store.expect("missing store").actions().as_slice(),
4110            [
4111                DataStoreAction::StoreOptions { opts },
4112                DataStoreAction::LoadOptions
4113            ] if opts.is_empty()
4114        );
4115    }
4116
4117    #[test]
4118    fn server_dispatcher_reset_parameters() {
4119        let mut server = new_test_minimal_server();
4120        let default_params = test_server_params(
4121            vec![std_ip_v4!("192.168.0.1")],
4122            LeaseLength { default_seconds: 86400, max_seconds: 86400 },
4123        )
4124        .expect("failed to get test server parameters");
4125        assert_ne!(default_params, server.params);
4126        let () =
4127            server.dispatch_reset_parameters(&default_params).expect("failed to reset parameters");
4128        assert_eq!(default_params, server.params);
4129        assert_matches::assert_matches!(
4130            server.store.expect("missing store").actions().as_slice(),
4131            [DataStoreAction::StoreParameters { params }] if *params == default_params
4132        );
4133    }
4134
4135    #[test]
4136    fn server_dispatcher_clear_leases() {
4137        let mut server = new_test_minimal_server();
4138        server.params.managed_addrs.pool_range_stop = std_ip_v4!("192.168.0.4");
4139        server.pool = AddressPool::new(server.params.managed_addrs.pool_range());
4140        let client = std_ip_v4!("192.168.0.2");
4141        server
4142            .pool
4143            .allocate_addr(client)
4144            .unwrap_or_else(|err| panic!("allocate_addr({}) failed: {:?}", client, err));
4145        let client_id = ClientIdentifier::from(random_mac_generator());
4146        server.records = [(
4147            client_id.clone(),
4148            LeaseRecord {
4149                current: Some(client),
4150                previous: None,
4151                options: Vec::new(),
4152                lease_start_epoch_seconds: 0,
4153                lease_length_seconds: 42,
4154            },
4155        )]
4156        .into();
4157        server.dispatch_clear_leases().expect("dispatch_clear_leases() failed");
4158        let empty_map = HashMap::new();
4159        assert_eq!(empty_map, server.records);
4160        assert!(server.pool.addr_is_available(client));
4161        assert!(!server.pool.addr_is_allocated(client));
4162        let stored_leases = server
4163            .store
4164            .as_mut()
4165            .expect("missing store")
4166            .load_client_records()
4167            .expect("load_client_records() failed");
4168        assert_eq!(empty_map, stored_leases);
4169        assert_matches::assert_matches!(
4170            server.store.expect("missing store").actions().as_slice(),
4171            [
4172                DataStoreAction::Delete { client_id: id },
4173                DataStoreAction::LoadClientRecords
4174            ] if *id == client_id
4175        );
4176    }
4177
4178    #[test]
4179    fn server_dispatcher_validate_params() {
4180        let mut server = new_test_minimal_server();
4181        server.pool.universe.clear();
4182        assert_eq!(server.try_validate_parameters(), Err(Status::INVALID_ARGS));
4183    }
4184
4185    #[test]
4186    fn set_address_pool_fails_if_leases_present() {
4187        let mut server = new_test_minimal_server();
4188        assert_matches::assert_matches!(
4189            server.records.insert(
4190                ClientIdentifier::from(MacAddr::new([1, 2, 3, 4, 5, 6])),
4191                LeaseRecord::default(),
4192            ),
4193            None
4194        );
4195        assert_eq!(
4196            server.dispatch_set_parameter(fidl_fuchsia_net_dhcp::Parameter::AddressPool(
4197                fidl_fuchsia_net_dhcp::AddressPool {
4198                    prefix_length: Some(24),
4199                    range_start: Some(fidl_ip_v4!("192.168.0.2")),
4200                    range_stop: Some(fidl_ip_v4!("192.168.0.254")),
4201                    ..Default::default()
4202                }
4203            )),
4204            Err(Status::BAD_STATE)
4205        );
4206    }
4207
4208    #[test]
4209    fn set_address_pool_updates_internal_pool() {
4210        let mut server = new_test_minimal_server();
4211        server.pool.universe.clear();
4212        server
4213            .dispatch_set_parameter(fidl_fuchsia_net_dhcp::Parameter::AddressPool(
4214                fidl_fuchsia_net_dhcp::AddressPool {
4215                    prefix_length: Some(24),
4216                    range_start: Some(fidl_ip_v4!("192.168.0.2")),
4217                    range_stop: Some(fidl_ip_v4!("192.168.0.5")),
4218                    ..Default::default()
4219                },
4220            ))
4221            .expect("failed to set parameter");
4222        assert_eq!(server.pool.available().count(), 3);
4223        assert_matches::assert_matches!(
4224            server.store.expect("missing store").actions().as_slice(),
4225            [DataStoreAction::StoreParameters { params }] if *params == server.params
4226        );
4227    }
4228
4229    #[test]
4230    fn recovery_from_expired_persistent_record() {
4231        let client_ip = net_declare::std::ip_v4!("192.168.0.1");
4232        let mut time_source = TestSystemTime::with_current_time();
4233        const LEASE_EXPIRATION_SECONDS: u32 = 60;
4234        // The previous server has stored a stale client record.
4235        let mut store = ActionRecordingDataStore::new();
4236        let client_id = ClientIdentifier::from(random_mac_generator());
4237        let client_record = LeaseRecord::new(
4238            Some(client_ip),
4239            Vec::new(),
4240            time_source.now(),
4241            LEASE_EXPIRATION_SECONDS,
4242        )
4243        .expect("failed to create lease record");
4244        store.insert(&client_id, &client_record).expect("failed to insert client record");
4245        // The record should become expired now.
4246        time_source.move_forward(Duration::from_secs(LEASE_EXPIRATION_SECONDS.into()));
4247
4248        // Only 192.168.0.1 is available.
4249        let params = ServerParameters {
4250            server_ips: Vec::new(),
4251            lease_length: LeaseLength {
4252                default_seconds: 60 * 60 * 24,
4253                max_seconds: 60 * 60 * 24 * 7,
4254            },
4255            managed_addrs: ManagedAddresses {
4256                mask: SubnetMask::new(prefix_length_v4!(24)),
4257                pool_range_start: client_ip,
4258                pool_range_stop: net_declare::std::ip_v4!("192.168.0.2"),
4259            },
4260            permitted_macs: PermittedMacs(Vec::new()),
4261            static_assignments: StaticAssignments(HashMap::new()),
4262            arp_probe: false,
4263            bound_device_names: Vec::new(),
4264        };
4265
4266        // The server should recover to a consistent state on the next start.
4267        let records: HashMap<_, _> =
4268            Some((client_id.clone(), client_record.clone())).into_iter().collect();
4269        let server: Server =
4270            Server::new_with_time_source(store, params, HashMap::new(), records, time_source)
4271                .expect("failed to create server");
4272        // Create a temporary because assert_matches! doesn't like turbo-fish type annotation.
4273        let contents: Vec<(&ClientIdentifier, &LeaseRecord)> = server.records.iter().collect();
4274        assert_matches::assert_matches!(
4275            contents.as_slice(),
4276            [(id, LeaseRecord {current: None, previous: Some(ip), ..})] if **id == client_id && *ip == client_ip
4277        );
4278        assert!(server.pool.allocated.is_empty());
4279
4280        assert_eq!(server.pool.available().collect::<Vec<_>>(), vec![client_ip]);
4281
4282        assert_matches::assert_matches!(
4283            server.store.expect("missing store").actions().as_slice(),
4284            [
4285                DataStoreAction::StoreClientRecord{ client_id: id1, record },
4286                DataStoreAction::StoreClientRecord{ client_id: id2, record: LeaseRecord {current: None, previous: Some(ip), ..} },
4287            ] if id1 == id2 && id2 == &client_id && record == &client_record && *ip == client_ip
4288        );
4289    }
4290
4291    #[test]
4292    fn test_validate_discover() {
4293        use std::string::ToString as _;
4294        let mut disc = new_test_discover();
4295        disc.op = OpCode::BOOTREPLY;
4296        assert_eq!(
4297            validate_discover(&disc),
4298            Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
4299                field: String::from("op"),
4300                value: String::from("BOOTREPLY"),
4301                msg_type: MessageType::DHCPDISCOVER
4302            }))
4303        );
4304        disc = new_test_discover();
4305        disc.ciaddr = random_ipv4_generator();
4306        assert_eq!(
4307            validate_discover(&disc),
4308            Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
4309                field: String::from("ciaddr"),
4310                value: disc.ciaddr.to_string(),
4311                msg_type: MessageType::DHCPDISCOVER
4312            }))
4313        );
4314        disc = new_test_discover();
4315        disc.yiaddr = random_ipv4_generator();
4316        assert_eq!(
4317            validate_discover(&disc),
4318            Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
4319                field: String::from("yiaddr"),
4320                value: disc.yiaddr.to_string(),
4321                msg_type: MessageType::DHCPDISCOVER
4322            }))
4323        );
4324        disc = new_test_discover();
4325        disc.siaddr = random_ipv4_generator();
4326        assert_eq!(
4327            validate_discover(&disc),
4328            Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
4329                field: String::from("siaddr"),
4330                value: disc.siaddr.to_string(),
4331                msg_type: MessageType::DHCPDISCOVER
4332            }))
4333        );
4334        disc = new_test_discover();
4335        let server = random_ipv4_generator();
4336        disc.options.push(DhcpOption::ServerIdentifier(server));
4337        assert_eq!(
4338            validate_discover(&disc),
4339            Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
4340                field: String::from("ServerIdentifier"),
4341                value: server.to_string(),
4342                msg_type: MessageType::DHCPDISCOVER
4343            }))
4344        );
4345        disc = new_test_discover();
4346        assert_eq!(validate_discover(&disc), Ok(()));
4347    }
4348
4349    #[test]
4350    fn build_offer_with_custom_t1_t2() {
4351        let mut server = new_test_minimal_server();
4352        let initial_disc = new_test_discover();
4353        let initial_offer_ip = random_ipv4_generator();
4354        assert!(server.pool.universe.insert(initial_offer_ip));
4355        let offer =
4356            server.build_offer(initial_disc, initial_offer_ip).expect("failed to build offer");
4357        let v = offer.options.iter().find_map(|v| match v {
4358            DhcpOption::RenewalTimeValue(v) => Some(*v),
4359            _ => None,
4360        });
4361        assert_eq!(
4362            v,
4363            Some(server.params.lease_length.default_seconds / 2),
4364            "offer options did not contain expected renewal time: {:?}",
4365            offer.options
4366        );
4367        let v = offer.options.iter().find_map(|v| match v {
4368            DhcpOption::RebindingTimeValue(v) => Some(*v),
4369            _ => None,
4370        });
4371        assert_eq!(
4372            v,
4373            Some((server.params.lease_length.default_seconds * 3) / 4),
4374            "offer options did not contain expected rebinding time: {:?}",
4375            offer.options
4376        );
4377        let t1 = rand::random::<u32>();
4378        assert_matches::assert_matches!(
4379            server
4380                .options_repo
4381                .insert(OptionCode::RenewalTimeValue, DhcpOption::RenewalTimeValue(t1)),
4382            None
4383        );
4384        let t2 = rand::random::<u32>();
4385        assert_matches::assert_matches!(
4386            server
4387                .options_repo
4388                .insert(OptionCode::RebindingTimeValue, DhcpOption::RebindingTimeValue(t2)),
4389            None
4390        );
4391        let disc = new_test_discover();
4392        let offer_ip = random_ipv4_generator();
4393        assert!(server.pool.universe.insert(offer_ip));
4394        let offer = server.build_offer(disc, offer_ip).expect("failed to build offer");
4395        let v = offer.options.iter().find_map(|v| match v {
4396            DhcpOption::RenewalTimeValue(v) => Some(*v),
4397            _ => None,
4398        });
4399        assert_eq!(
4400            v,
4401            Some(t1),
4402            "offer options did not contain expected renewal time: {:?}",
4403            offer.options
4404        );
4405        let v = offer.options.iter().find_map(|v| match v {
4406            DhcpOption::RebindingTimeValue(v) => Some(*v),
4407            _ => None,
4408        });
4409        assert_eq!(
4410            v,
4411            Some(t2),
4412            "offer options did not contain expected rebinding time: {:?}",
4413            offer.options
4414        );
4415    }
4416
4417    #[test_case(None; "no requested lease length")]
4418    #[test_case(Some(150); "requested lease length under maximum")]
4419    #[test_case(Some(1000); "requested lease length above maximum")]
4420    fn standalone_build_offer(requested_lease_length: Option<u32>) {
4421        let discover = new_test_discover_with_options(
4422            requested_lease_length.map(DhcpOption::IpAddressLeaseTime).into_iter(),
4423        );
4424        let offered_ip = random_ipv4_generator();
4425        let subnet_mask = DEFAULT_PREFIX_LENGTH;
4426        let server_ip = random_ipv4_generator();
4427        const DEFAULT_LEASE_LENGTH_SECONDS: u32 = 100;
4428        const MAX_LEASE_LENGTH_SECONDS: u32 = 200;
4429        let lease_length_config = LeaseLength {
4430            default_seconds: DEFAULT_LEASE_LENGTH_SECONDS,
4431            max_seconds: MAX_LEASE_LENGTH_SECONDS,
4432        };
4433        let expected_lease_length = match requested_lease_length {
4434            None => DEFAULT_LEASE_LENGTH_SECONDS,
4435            Some(x) => x.min(MAX_LEASE_LENGTH_SECONDS),
4436        };
4437
4438        let chaddr = discover.chaddr;
4439        let xid = discover.xid;
4440        let bdcast_flag = discover.bdcast_flag;
4441
4442        assert_eq!(
4443            build_offer(
4444                discover,
4445                OfferOptions {
4446                    offered_ip,
4447                    server_ip,
4448                    lease_length_config,
4449                    renewal_time_value: None,
4450                    rebinding_time_value: None,
4451                    subnet_mask,
4452                },
4453                &options_repo([]),
4454            )
4455            .expect("build_offer should succeed"),
4456            Message {
4457                op: OpCode::BOOTREPLY,
4458                xid,
4459                secs: 0,
4460                bdcast_flag,
4461                ciaddr: Ipv4Addr::UNSPECIFIED,
4462                yiaddr: offered_ip,
4463                siaddr: Ipv4Addr::UNSPECIFIED,
4464                giaddr: Ipv4Addr::UNSPECIFIED,
4465                chaddr,
4466                sname: BString::default(),
4467                file: BString::default(),
4468                options: vec![
4469                    DhcpOption::DhcpMessageType(MessageType::DHCPOFFER),
4470                    DhcpOption::ServerIdentifier(server_ip),
4471                    DhcpOption::IpAddressLeaseTime(expected_lease_length),
4472                    DhcpOption::RenewalTimeValue(expected_lease_length / 2),
4473                    DhcpOption::RebindingTimeValue(expected_lease_length * 3 / 4),
4474                    DhcpOption::SubnetMask(subnet_mask),
4475                ],
4476            }
4477        );
4478    }
4479}