1use 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
29pub 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
42pub trait SystemTimeSource {
44 fn with_current_time() -> Self;
45 fn now(&self) -> std::time::SystemTime;
46}
47
48pub 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
61pub trait DataStore {
63 type Error: std::error::Error + std::marker::Send + std::marker::Sync + 'static;
64
65 fn insert(
67 &mut self,
68 client_id: &ClientIdentifier,
69 record: &LeaseRecord,
70 ) -> Result<(), Self::Error>;
71
72 fn store_options(&mut self, opts: &[DhcpOption]) -> Result<(), Self::Error>;
74
75 fn store_parameters(&mut self, params: &ServerParameters) -> Result<(), Self::Error>;
77
78 fn delete(&mut self, client_id: &ClientIdentifier) -> Result<(), Self::Error>;
80}
81
82pub const DEFAULT_STASH_ID: &str = "dhcpd";
84
85#[derive(Debug, PartialEq)]
94pub enum ServerAction {
95 SendResponse(Message, ResponseTarget),
96 AddressDecline(Ipv4Addr),
97 AddressRelease(Ipv4Addr),
98}
99
100#[derive(Debug, PartialEq)]
107pub enum ResponseTarget {
108 Broadcast,
109 Unicast(Ipv4Addr, Option<MacAddr>),
110}
111
112#[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 MissingServerIdentifier,
167
168 #[error("unable to get system time")]
169 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#[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 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 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 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 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 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 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 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 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 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 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 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 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 *record = refreshed;
505 Ok(())
506 }
507
508 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 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(¶ms.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 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 if record.current.as_ref().is_some_and(|cur| cur == &client_ip) {
640 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 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 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 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 None => Ok(*self.params.server_ips.first().ok_or(ServerError::ServerMissingIpAddr)?),
792 }
793 }
794
795 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 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 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
836pub 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
843pub 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
853pub 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
903pub fn get_requested_options(
906 client_opts: &[DhcpOption],
907 options_repo: &HashMap<OptionCode, DhcpOption>,
908 subnet_mask: PrefixLength<Ipv4>,
909) -> Vec<DhcpOption> {
910 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 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 break;
948 }
949 _ => continue,
950 }
951 }
952
953 offered_opts
954 })
955}
956
957fn 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")]
979pub trait ServerDispatcher {
986 fn try_validate_parameters(&self) -> Result<&ServerParameters, Status>;
989
990 fn dispatch_get_option(
992 &self,
993 code: fidl_fuchsia_net_dhcp::OptionCode,
994 ) -> Result<fidl_fuchsia_net_dhcp::Option_, Status>;
995 fn dispatch_get_parameter(
997 &self,
998 name: fidl_fuchsia_net_dhcp::ParameterName,
999 ) -> Result<fidl_fuchsia_net_dhcp::Parameter, Status>;
1000 fn dispatch_set_option(&mut self, value: fidl_fuchsia_net_dhcp::Option_) -> Result<(), Status>;
1002 fn dispatch_set_parameter(
1004 &mut self,
1005 value: fidl_fuchsia_net_dhcp::Parameter,
1006 ) -> Result<(), Status>;
1007 fn dispatch_list_options(&self) -> Result<Vec<fidl_fuchsia_net_dhcp::Option_>, Status>;
1009 fn dispatch_list_parameters(&self) -> Result<Vec<fidl_fuchsia_net_dhcp::Parameter>, Status>;
1011 fn dispatch_reset_options(&mut self) -> Result<(), Status>;
1013 fn dispatch_reset_parameters(&mut self, defaults: &ServerParameters) -> Result<(), Status>;
1015 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 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 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 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 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 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
1268pub type ClientRecords = HashMap<ClientIdentifier, LeaseRecord>;
1273
1274#[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 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 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#[derive(Debug)]
1365struct AddressPool {
1366 universe: BTreeSet<Ipv4Addr>,
1371 allocated: BTreeSet<Ipv4Addr>,
1372}
1373
1374#[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
1441fn 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 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 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 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 #[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 #[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 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 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 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 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 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 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 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 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 req.options.push(DhcpOption::RequestedIpAddress(random_ipv4_generator()));
2922
2923 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 time_source.move_forward(Duration::from_secs(LEASE_EXPIRATION_SECONDS.into()));
4423
4424 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 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 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}