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