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