Skip to main content

dhcpv4/
server.rs

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