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