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