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