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