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