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