1use dhcp_protocol::{AtLeast, AtMostBytes};
8use diagnostics_traits::Inspector;
9use packet::{InnerPacketBuilder, ParseBuffer as _, Serializer};
10use packet_formats::ip::IpPacket as _;
11use std::net::Ipv4Addr;
12use std::num::{NonZeroU16, NonZeroU32, TryFromIntError};
13
14use crate::inspect::Counter;
15
16#[derive(thiserror::Error, Debug)]
17pub(crate) enum ParseError {
18 #[error("parsing IPv4 packet")]
19 Ipv4(packet_formats::error::IpParseError<net_types::ip::Ipv4>),
20 #[error("IPv4 packet protocol was not UDP")]
21 NotUdp,
22 #[error("parsing UDP datagram")]
23 Udp(packet_formats::error::ParseError),
24 #[error("incoming packet destined for wrong port")]
25 WrongPort(NonZeroU16),
26 #[error("incoming packet has wrong source address")]
27 WrongSource(std::net::SocketAddr),
28 #[error("parsing DHCP message")]
29 Dhcp(dhcp_protocol::ProtocolError),
30}
31
32pub(crate) fn parse_dhcp_message_from_ip_packet(
37 mut bytes: &[u8],
38 expected_dst_port: NonZeroU16,
39) -> Result<(net_types::ip::Ipv4Addr, dhcp_protocol::Message), ParseError> {
40 let ip_packet =
41 bytes.parse::<packet_formats::ipv4::Ipv4Packet<_>>().map_err(ParseError::Ipv4)?;
42
43 let src_addr = ip_packet.src_ip();
44
45 match ip_packet.proto() {
46 packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Udp) => (),
47 packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Tcp)
48 | packet_formats::ip::Ipv4Proto::Icmp
49 | packet_formats::ip::Ipv4Proto::Igmp
50 | packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Reserved)
51 | packet_formats::ip::Ipv4Proto::Other(_) => return Err(ParseError::NotUdp),
52 };
53 let mut ip_packet_body = ip_packet.body();
54
55 let udp_packet = ip_packet_body
56 .parse_with::<_, packet_formats::udp::UdpPacket<_>>(packet_formats::udp::UdpParseArgs::new(
57 ip_packet.src_ip(),
58 ip_packet.dst_ip(),
59 ))
60 .map_err(ParseError::Udp)?;
61 let dst_port = udp_packet.dst_port();
62 if dst_port != expected_dst_port {
63 return Err(ParseError::WrongPort(dst_port));
64 }
65 dhcp_protocol::Message::from_buffer(udp_packet.body())
66 .map(|msg| (src_addr, msg))
67 .map_err(ParseError::Dhcp)
68}
69
70const DEFAULT_TTL: u8 = 64;
71
72pub(crate) fn serialize_dhcp_message_to_ip_packet(
75 message: dhcp_protocol::Message,
76 src_ip: impl Into<net_types::ip::Ipv4Addr>,
77 src_port: NonZeroU16,
78 dst_ip: impl Into<net_types::ip::Ipv4Addr>,
79 dst_port: NonZeroU16,
80) -> impl AsRef<[u8]> {
81 let message = message.serialize();
82 let src_ip = src_ip.into();
83 let dst_ip = dst_ip.into();
84
85 let udp_builder =
86 packet_formats::udp::UdpPacketBuilder::new(src_ip, dst_ip, Some(src_port), dst_port);
87
88 let ipv4_builder = packet_formats::ipv4::Ipv4PacketBuilder::new(
89 src_ip,
90 dst_ip,
91 DEFAULT_TTL,
92 packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Udp),
93 );
94
95 match message.into_serializer().wrap_in(udp_builder).wrap_in(ipv4_builder).serialize_vec_outer()
96 {
97 Ok(buf) => buf,
98 Err(e) => {
99 let (e, _serializer) = e;
100 match e {
101 packet::SerializeError::SizeLimitExceeded => {
102 unreachable!("no MTU constraints on serializer")
103 }
104 }
105 }
106 }
107}
108
109#[derive(derive_builder::Builder, Debug, PartialEq)]
110#[builder(private, build_fn(error = "CommonIncomingMessageError"))]
111struct CommonIncomingMessageFields {
112 message_type: dhcp_protocol::MessageType,
113 #[builder(setter(custom), default)]
114 server_identifier: Option<net_types::SpecifiedAddr<net_types::ip::Ipv4Addr>>,
115 #[builder(setter(custom), default)]
116 yiaddr: Option<net_types::SpecifiedAddr<net_types::ip::Ipv4Addr>>,
117 #[builder(setter(strip_option), default)]
118 ip_address_lease_time_secs: Option<NonZeroU32>,
119 #[builder(setter(strip_option), default)]
123 renewal_time_value_secs: Option<u32>,
124 #[builder(setter(strip_option), default)]
126 rebinding_time_value_secs: Option<u32>,
127 #[builder(default)]
128 parameters: Vec<dhcp_protocol::DhcpOption>,
129 #[builder(setter(strip_option), default)]
130 message: Option<String>,
131 #[builder(setter(strip_option), default)]
132 client_identifier: Option<AtLeast<2, AtMostBytes<{ dhcp_protocol::U8_MAX_AS_USIZE }, Vec<u8>>>>,
133 #[builder(setter(custom))]
134 seen_option_codes: OptionCodeSet,
135}
136
137#[derive(thiserror::Error, Debug, PartialEq)]
138pub(crate) enum CommonIncomingMessageError {
139 #[error("got op = {0}, want op = BOOTREPLY")]
140 NotBootReply(dhcp_protocol::OpCode),
141 #[error("server identifier was the unspecified address")]
142 UnspecifiedServerIdentifier,
143 #[error("missing: {0}")]
144 BuilderMissingField(&'static str),
145 #[error("duplicate option: {0:?}")]
146 DuplicateOption(dhcp_protocol::OptionCode),
147 #[error("option's inclusion violates protocol: {0:?}")]
148 IllegallyIncludedOption(dhcp_protocol::OptionCode),
149}
150
151impl From<derive_builder::UninitializedFieldError> for CommonIncomingMessageError {
152 fn from(value: derive_builder::UninitializedFieldError) -> Self {
153 Self::BuilderMissingField(value.field_name())
156 }
157}
158
159#[derive(Default, Debug)]
161pub(crate) struct CommonIncomingMessageErrorCounters {
162 pub(crate) not_boot_reply: Counter,
164 pub(crate) unspecified_server_identifier: Counter,
167 pub(crate) parser_missing_field: Counter,
170 pub(crate) duplicate_option: Counter,
173 pub(crate) illegally_included_option: Counter,
176}
177
178impl CommonIncomingMessageErrorCounters {
179 fn record(&self, inspector: &mut impl Inspector) {
181 let Self {
182 not_boot_reply,
183 unspecified_server_identifier,
184 parser_missing_field,
185 duplicate_option,
186 illegally_included_option,
187 } = self;
188 inspector.record_usize("NotBootReply", not_boot_reply.load());
189 inspector.record_usize("UnspecifiedServerIdentifier", unspecified_server_identifier.load());
190 inspector.record_usize("ParserMissingField", parser_missing_field.load());
191 inspector.record_usize("DuplicateOption", duplicate_option.load());
192 inspector.record_usize("IllegallyIncludedOption", illegally_included_option.load());
193 }
194
195 fn increment(&self, error: &CommonIncomingMessageError) {
197 let Self {
198 not_boot_reply,
199 unspecified_server_identifier,
200 parser_missing_field,
201 duplicate_option,
202 illegally_included_option,
203 } = self;
204 match error {
205 CommonIncomingMessageError::NotBootReply(_) => not_boot_reply.increment(),
206 CommonIncomingMessageError::UnspecifiedServerIdentifier => {
207 unspecified_server_identifier.increment()
208 }
209 CommonIncomingMessageError::BuilderMissingField(_) => parser_missing_field.increment(),
210 CommonIncomingMessageError::DuplicateOption(_) => duplicate_option.increment(),
211 CommonIncomingMessageError::IllegallyIncludedOption(_) => {
212 illegally_included_option.increment()
213 }
214 }
215 }
216}
217
218impl CommonIncomingMessageFieldsBuilder {
219 fn ignore_unused_result(&mut self) {}
220
221 fn add_requested_parameter(&mut self, option: dhcp_protocol::DhcpOption) {
222 let parameters = self.parameters.get_or_insert_with(Default::default);
223 parameters.push(option)
224 }
225
226 fn add_seen_option_and_return_whether_newly_added(
227 &mut self,
228 option_code: dhcp_protocol::OptionCode,
229 ) -> bool {
230 self.seen_option_codes.get_or_insert_with(Default::default).insert(option_code)
231 }
232
233 fn server_identifier(&mut self, addr: Ipv4Addr) -> Result<(), CommonIncomingMessageError> {
234 self.server_identifier = Some(Some(
235 net_types::SpecifiedAddr::new(net_types::ip::Ipv4Addr::from(addr))
236 .ok_or(CommonIncomingMessageError::UnspecifiedServerIdentifier)?,
237 ));
238 Ok(())
239 }
240
241 fn yiaddr(&mut self, addr: Ipv4Addr) {
242 match net_types::SpecifiedAddr::new(net_types::ip::Ipv4Addr::from(addr)) {
243 None => {
244 }
250 Some(specified_addr) => {
251 self.yiaddr = Some(Some(specified_addr));
252 }
253 }
254 }
255}
256
257#[derive(Clone, PartialEq, Debug)]
259pub struct OptionCodeMap<T> {
260 inner: [Option<T>; dhcp_protocol::U8_MAX_AS_USIZE],
261}
262
263impl<T: Copy> OptionCodeMap<T> {
264 pub fn new() -> Self {
266 OptionCodeMap { inner: [None; dhcp_protocol::U8_MAX_AS_USIZE] }
267 }
268
269 pub fn put(&mut self, option_code: dhcp_protocol::OptionCode, value: T) -> Option<T> {
272 std::mem::replace(&mut self.inner[usize::from(u8::from(option_code))], Some(value))
273 }
274
275 pub fn get(&self, option_code: dhcp_protocol::OptionCode) -> Option<T> {
277 self.inner[usize::from(u8::from(option_code))]
278 }
279
280 pub fn contains(&self, option_code: dhcp_protocol::OptionCode) -> bool {
282 self.get(option_code).is_some()
283 }
284
285 pub(crate) fn iter(&self) -> impl Iterator<Item = (dhcp_protocol::OptionCode, T)> + '_ {
286 self.inner.iter().enumerate().filter_map(|(index, value)| {
287 let option_code = u8::try_from(index)
288 .ok()
289 .and_then(|i| dhcp_protocol::OptionCode::try_from(i).ok())?;
290 let value = *value.as_ref()?;
291 Some((option_code, value))
292 })
293 }
294
295 pub(crate) fn iter_keys(&self) -> impl Iterator<Item = dhcp_protocol::OptionCode> + '_ {
296 self.iter().map(|(key, _)| key)
297 }
298}
299
300impl<V: Copy> FromIterator<(dhcp_protocol::OptionCode, V)> for OptionCodeMap<V> {
301 fn from_iter<T: IntoIterator<Item = (dhcp_protocol::OptionCode, V)>>(iter: T) -> Self {
302 let mut map = Self::new();
303 for (option_code, value) in iter {
304 let _: Option<_> = map.put(option_code, value);
305 }
306 map
307 }
308}
309
310impl<T: Copy> Default for OptionCodeMap<T> {
311 fn default() -> Self {
312 Self::new()
313 }
314}
315
316impl OptionCodeMap<OptionRequested> {
317 fn iter_required(&self) -> impl Iterator<Item = dhcp_protocol::OptionCode> + '_ {
318 self.iter().filter_map(|(key, val)| match val {
319 OptionRequested::Required => Some(key),
320 OptionRequested::Optional => None,
321 })
322 }
323
324 pub(crate) fn try_to_parameter_request_list(
329 &self,
330 ) -> Option<
331 AtLeast<1, AtMostBytes<{ dhcp_protocol::U8_MAX_AS_USIZE }, Vec<dhcp_protocol::OptionCode>>>,
332 > {
333 match AtLeast::try_from(self.iter_keys().collect::<Vec<_>>()) {
334 Ok(parameters) => Some(parameters),
335 Err((dhcp_protocol::SizeConstrainedError::SizeConstraintViolated, parameters)) => {
336 assert_eq!(parameters, Vec::new());
338 None
340 }
341 }
342 }
343}
344
345pub type OptionCodeSet = OptionCodeMap<()>;
347
348impl OptionCodeSet {
349 pub fn insert(&mut self, option_code: dhcp_protocol::OptionCode) -> bool {
351 self.put(option_code, ()).is_none()
352 }
353}
354
355impl FromIterator<dhcp_protocol::OptionCode> for OptionCodeSet {
356 fn from_iter<T: IntoIterator<Item = dhcp_protocol::OptionCode>>(iter: T) -> Self {
357 let mut set = Self::new();
358 for code in iter {
359 let _: bool = set.insert(code);
360 }
361 set
362 }
363}
364
365#[derive(Copy, Clone, PartialEq, Debug)]
367pub enum OptionRequested {
368 Required,
371 Optional,
373}
374
375fn collect_common_fields<T: Copy>(
376 requested_parameters: &OptionCodeMap<T>,
377 dhcp_protocol::Message {
378 op,
379 xid: _,
380 secs: _,
381 bdcast_flag: _,
382 ciaddr: _,
383 yiaddr,
384 siaddr: _,
385 giaddr: _,
386 chaddr: _,
387 sname: _,
388 file: _,
389 options,
390 }: dhcp_protocol::Message,
391) -> Result<CommonIncomingMessageFields, CommonIncomingMessageError> {
392 use dhcp_protocol::DhcpOption;
393
394 match op {
395 dhcp_protocol::OpCode::BOOTREQUEST => {
396 return Err(CommonIncomingMessageError::NotBootReply(op))
397 }
398 dhcp_protocol::OpCode::BOOTREPLY => (),
399 };
400
401 let mut builder = CommonIncomingMessageFieldsBuilder::default();
402 builder.yiaddr(yiaddr);
403
404 for option in options {
405 let newly_seen = builder.add_seen_option_and_return_whether_newly_added(option.code());
406 if !newly_seen {
407 return Err(CommonIncomingMessageError::DuplicateOption(option.code()));
408 }
409
410 match &option {
431 DhcpOption::IpAddressLeaseTime(value) => match NonZeroU32::try_from(*value) {
432 Err(e) => {
433 let _: TryFromIntError = e;
434 log::warn!("dropping 0 lease time");
435 }
436 Ok(value) => {
437 builder.ip_address_lease_time_secs(value).ignore_unused_result();
438 }
439 },
440 DhcpOption::DhcpMessageType(message_type) => {
441 builder.message_type(*message_type).ignore_unused_result()
442 }
443 DhcpOption::ServerIdentifier(value) => {
444 builder.server_identifier(*value)?;
445 }
446 DhcpOption::Message(message) => builder.message(message.clone()).ignore_unused_result(),
447 DhcpOption::RenewalTimeValue(value) => {
448 builder.renewal_time_value_secs(*value).ignore_unused_result()
449 }
450 DhcpOption::RebindingTimeValue(value) => {
451 builder.rebinding_time_value_secs(*value).ignore_unused_result()
452 }
453 DhcpOption::ClientIdentifier(value) => {
454 builder.client_identifier(value.clone()).ignore_unused_result();
455 }
456 DhcpOption::ParameterRequestList(_)
457 | DhcpOption::RequestedIpAddress(_)
458 | DhcpOption::MaxDhcpMessageSize(_) => {
459 return Err(CommonIncomingMessageError::IllegallyIncludedOption(option.code()))
460 }
461 DhcpOption::Pad()
462 | DhcpOption::End()
463 | DhcpOption::SubnetMask(_)
464 | DhcpOption::TimeOffset(_)
465 | DhcpOption::Router(_)
466 | DhcpOption::TimeServer(_)
467 | DhcpOption::NameServer(_)
468 | DhcpOption::DomainNameServer(_)
469 | DhcpOption::LogServer(_)
470 | DhcpOption::CookieServer(_)
471 | DhcpOption::LprServer(_)
472 | DhcpOption::ImpressServer(_)
473 | DhcpOption::ResourceLocationServer(_)
474 | DhcpOption::HostName(_)
475 | DhcpOption::BootFileSize(_)
476 | DhcpOption::MeritDumpFile(_)
477 | DhcpOption::DomainName(_)
478 | DhcpOption::SwapServer(_)
479 | DhcpOption::RootPath(_)
480 | DhcpOption::ExtensionsPath(_)
481 | DhcpOption::IpForwarding(_)
482 | DhcpOption::NonLocalSourceRouting(_)
483 | DhcpOption::PolicyFilter(_)
484 | DhcpOption::MaxDatagramReassemblySize(_)
485 | DhcpOption::DefaultIpTtl(_)
486 | DhcpOption::PathMtuAgingTimeout(_)
487 | DhcpOption::PathMtuPlateauTable(_)
488 | DhcpOption::InterfaceMtu(_)
489 | DhcpOption::AllSubnetsLocal(_)
490 | DhcpOption::BroadcastAddress(_)
491 | DhcpOption::PerformMaskDiscovery(_)
492 | DhcpOption::MaskSupplier(_)
493 | DhcpOption::PerformRouterDiscovery(_)
494 | DhcpOption::RouterSolicitationAddress(_)
495 | DhcpOption::StaticRoute(_)
496 | DhcpOption::TrailerEncapsulation(_)
497 | DhcpOption::ArpCacheTimeout(_)
498 | DhcpOption::EthernetEncapsulation(_)
499 | DhcpOption::TcpDefaultTtl(_)
500 | DhcpOption::TcpKeepaliveInterval(_)
501 | DhcpOption::TcpKeepaliveGarbage(_)
502 | DhcpOption::NetworkInformationServiceDomain(_)
503 | DhcpOption::NetworkInformationServers(_)
504 | DhcpOption::NetworkTimeProtocolServers(_)
505 | DhcpOption::VendorSpecificInformation(_)
506 | DhcpOption::NetBiosOverTcpipNameServer(_)
507 | DhcpOption::NetBiosOverTcpipDatagramDistributionServer(_)
508 | DhcpOption::NetBiosOverTcpipNodeType(_)
509 | DhcpOption::NetBiosOverTcpipScope(_)
510 | DhcpOption::XWindowSystemFontServer(_)
511 | DhcpOption::XWindowSystemDisplayManager(_)
512 | DhcpOption::NetworkInformationServicePlusDomain(_)
513 | DhcpOption::NetworkInformationServicePlusServers(_)
514 | DhcpOption::MobileIpHomeAgent(_)
515 | DhcpOption::SmtpServer(_)
516 | DhcpOption::Pop3Server(_)
517 | DhcpOption::NntpServer(_)
518 | DhcpOption::DefaultWwwServer(_)
519 | DhcpOption::DefaultFingerServer(_)
520 | DhcpOption::DefaultIrcServer(_)
521 | DhcpOption::StreetTalkServer(_)
522 | DhcpOption::StreetTalkDirectoryAssistanceServer(_)
523 | DhcpOption::OptionOverload(_)
524 | DhcpOption::TftpServerName(_)
525 | DhcpOption::BootfileName(_)
526 | DhcpOption::VendorClassIdentifier(_) => (),
527 };
528
529 if requested_parameters.contains(option.code()) {
530 builder.add_requested_parameter(option);
531 }
532 }
533 builder.build()
534}
535
536#[derive(thiserror::Error, Debug, PartialEq)]
539pub(crate) enum SelectingIncomingMessageError {
540 #[error("{0}")]
541 CommonError(#[from] CommonIncomingMessageError),
542 #[error("no server identifier")]
547 NoServerIdentifier,
548 #[error("got DHCP message type = {0}, wanted DHCPOFFER")]
549 NotDhcpOffer(dhcp_protocol::MessageType),
550 #[error("yiaddr was the unspecified address")]
551 UnspecifiedYiaddr,
552 #[error("missing required option: {0:?}")]
553 MissingRequiredOption(dhcp_protocol::OptionCode),
554}
555
556#[derive(Default, Debug)]
559pub(crate) struct SelectingIncomingMessageErrorCounters {
560 pub(crate) common: CommonIncomingMessageErrorCounters,
562 pub(crate) no_server_identifier: Counter,
564 pub(crate) not_dhcp_offer: Counter,
566 pub(crate) unspecified_yiaddr: Counter,
568 pub(crate) missing_required_option: Counter,
570}
571
572impl SelectingIncomingMessageErrorCounters {
573 pub(crate) fn record(&self, inspector: &mut impl Inspector) {
575 let Self {
576 common,
577 no_server_identifier,
578 not_dhcp_offer,
579 unspecified_yiaddr,
580 missing_required_option,
581 } = self;
582 common.record(inspector);
583 inspector.record_usize("NoServerIdentifier", no_server_identifier.load());
584 inspector.record_usize("NotDhcpOffer", not_dhcp_offer.load());
585 inspector.record_usize("UnspecifiedYiaddr", unspecified_yiaddr.load());
586 inspector.record_usize("MissingRequiredOption", missing_required_option.load());
587 }
588
589 pub(crate) fn increment(&self, error: &SelectingIncomingMessageError) {
591 let Self {
592 common,
593 no_server_identifier,
594 not_dhcp_offer,
595 unspecified_yiaddr,
596 missing_required_option,
597 } = self;
598 match error {
599 SelectingIncomingMessageError::CommonError(common_incoming_message_error) => {
600 common.increment(common_incoming_message_error)
601 }
602 SelectingIncomingMessageError::NoServerIdentifier => no_server_identifier.increment(),
603 SelectingIncomingMessageError::NotDhcpOffer(_) => not_dhcp_offer.increment(),
604 SelectingIncomingMessageError::UnspecifiedYiaddr => unspecified_yiaddr.increment(),
605 SelectingIncomingMessageError::MissingRequiredOption(_) => {
606 missing_required_option.increment()
607 }
608 }
609 }
610}
611
612pub(crate) fn fields_to_retain_from_selecting(
615 requested_parameters: &OptionCodeMap<OptionRequested>,
616 message: dhcp_protocol::Message,
617) -> Result<FieldsFromOfferToUseInRequest, SelectingIncomingMessageError> {
618 let CommonIncomingMessageFields {
619 message_type,
620 server_identifier,
621 yiaddr,
622 ip_address_lease_time_secs,
623 renewal_time_value_secs: _,
624 rebinding_time_value_secs: _,
625 parameters: _,
626 seen_option_codes,
627 message: _,
628 client_identifier: _,
629 } = collect_common_fields(requested_parameters, message)?;
630
631 match message_type {
632 dhcp_protocol::MessageType::DHCPOFFER => (),
633 dhcp_protocol::MessageType::DHCPDISCOVER
634 | dhcp_protocol::MessageType::DHCPREQUEST
635 | dhcp_protocol::MessageType::DHCPDECLINE
636 | dhcp_protocol::MessageType::DHCPACK
637 | dhcp_protocol::MessageType::DHCPNAK
638 | dhcp_protocol::MessageType::DHCPRELEASE
639 | dhcp_protocol::MessageType::DHCPINFORM => {
640 return Err(SelectingIncomingMessageError::NotDhcpOffer(message_type))
641 }
642 };
643
644 if let Some(missing_option_code) =
645 requested_parameters.iter_required().find(|code| !seen_option_codes.contains(*code))
646 {
647 return Err(SelectingIncomingMessageError::MissingRequiredOption(missing_option_code));
648 }
649
650 Ok(FieldsFromOfferToUseInRequest {
651 server_identifier: server_identifier
652 .ok_or(SelectingIncomingMessageError::NoServerIdentifier)?,
653 ip_address_lease_time_secs,
654 ip_address_to_request: yiaddr.ok_or(SelectingIncomingMessageError::UnspecifiedYiaddr)?,
655 })
656}
657
658#[derive(Debug, Clone, Copy, PartialEq)]
659pub(crate) struct FieldsFromOfferToUseInRequest {
661 pub(crate) server_identifier: net_types::SpecifiedAddr<net_types::ip::Ipv4Addr>,
662 pub(crate) ip_address_lease_time_secs: Option<NonZeroU32>,
663 pub(crate) ip_address_to_request: net_types::SpecifiedAddr<net_types::ip::Ipv4Addr>,
664}
665
666impl FieldsFromOfferToUseInRequest {
667 pub(crate) fn record(&self, inspector: &mut impl Inspector) {
668 let Self { server_identifier, ip_address_lease_time_secs, ip_address_to_request } = self;
669 inspector.record_ip_addr("ServerIdentifier", **server_identifier);
670 if let Some(value) = ip_address_lease_time_secs {
671 inspector.record_uint("IpAddressLeaseTimeSecs", value.get());
672 }
673 inspector.record_ip_addr("IpAddressToRequest", **ip_address_to_request);
674 }
675}
676
677#[derive(Debug, PartialEq)]
678pub(crate) enum IncomingResponseToRequest<ServerIdentifier> {
684 Ack(FieldsToRetainFromAck<ServerIdentifier>),
685 Nak(FieldsToRetainFromNak),
686}
687
688#[derive(thiserror::Error, Debug, PartialEq)]
690pub(crate) enum IncomingResponseToRequestError {
691 #[error("{0}")]
692 CommonError(#[from] CommonIncomingMessageError),
693 #[error("got DHCP message type = {0}, wanted DHCPACK or DHCPNAK")]
694 NotDhcpAckOrNak(dhcp_protocol::MessageType),
695 #[error("yiaddr was the unspecified address")]
696 UnspecifiedYiaddr,
697 #[error("no IP address lease time")]
698 NoLeaseTime,
699 #[error("no server identifier")]
700 NoServerIdentifier,
701 #[error("missing required option: {0:?}")]
702 MissingRequiredOption(dhcp_protocol::OptionCode),
703}
704
705#[derive(Default, Debug)]
708pub(crate) struct IncomingResponseToRequestErrorCounters {
709 pub(crate) common: CommonIncomingMessageErrorCounters,
711 pub(crate) not_dhcp_ack_or_nak: Counter,
713 pub(crate) unspecified_yiaddr: Counter,
715 pub(crate) no_lease_time: Counter,
717 pub(crate) no_server_identifier: Counter,
719 pub(crate) missing_required_option: Counter,
721}
722
723impl IncomingResponseToRequestErrorCounters {
724 pub(crate) fn record(&self, inspector: &mut impl Inspector) {
726 let Self {
727 common,
728 not_dhcp_ack_or_nak,
729 unspecified_yiaddr,
730 no_lease_time,
731 no_server_identifier,
732 missing_required_option,
733 } = self;
734 common.record(inspector);
735 inspector.record_usize("NotDhcpAckOrNak", not_dhcp_ack_or_nak.load());
736 inspector.record_usize("UnspecifiedYiaddr", unspecified_yiaddr.load());
737 inspector.record_usize("NoLeaseTime", no_lease_time.load());
738 inspector.record_usize("NoServerIdentifier", no_server_identifier.load());
739 inspector.record_usize("MissingRequiredOption", missing_required_option.load());
740 }
741
742 pub(crate) fn increment(&self, error: &IncomingResponseToRequestError) {
744 let Self {
745 common,
746 not_dhcp_ack_or_nak,
747 unspecified_yiaddr,
748 no_lease_time,
749 no_server_identifier,
750 missing_required_option,
751 } = self;
752 match error {
753 IncomingResponseToRequestError::CommonError(common_incoming_message_error) => {
754 common.increment(common_incoming_message_error)
755 }
756 IncomingResponseToRequestError::NotDhcpAckOrNak(_) => not_dhcp_ack_or_nak.increment(),
757 IncomingResponseToRequestError::UnspecifiedYiaddr => unspecified_yiaddr.increment(),
758 IncomingResponseToRequestError::NoLeaseTime => no_lease_time.increment(),
759 IncomingResponseToRequestError::NoServerIdentifier => no_server_identifier.increment(),
760 IncomingResponseToRequestError::MissingRequiredOption(_) => {
761 missing_required_option.increment()
762 }
763 }
764 }
765}
766
767#[derive(Debug, PartialEq)]
768pub(crate) struct FieldsToRetainFromAck<ServerIdentifier> {
769 pub(crate) yiaddr: net_types::SpecifiedAddr<net_types::ip::Ipv4Addr>,
770 pub(crate) server_identifier: ServerIdentifier,
771 pub(crate) ip_address_lease_time_secs: NonZeroU32,
772 pub(crate) renewal_time_value_secs: Option<u32>,
773 pub(crate) rebinding_time_value_secs: Option<u32>,
774 pub(crate) parameters: Vec<dhcp_protocol::DhcpOption>,
775}
776
777impl<ServerIdentifier> FieldsToRetainFromAck<ServerIdentifier> {
778 pub(crate) fn map_server_identifier<T, E>(
779 self,
780 f: impl FnOnce(ServerIdentifier) -> Result<T, E>,
781 ) -> Result<FieldsToRetainFromAck<T>, E> {
782 let Self {
783 yiaddr,
784 server_identifier,
785 ip_address_lease_time_secs,
786 renewal_time_value_secs,
787 rebinding_time_value_secs,
788 parameters,
789 } = self;
790 Ok(FieldsToRetainFromAck {
791 yiaddr,
792 server_identifier: f(server_identifier)?,
793 ip_address_lease_time_secs,
794 renewal_time_value_secs,
795 rebinding_time_value_secs,
796 parameters,
797 })
798 }
799}
800
801#[derive(Debug, PartialEq)]
802pub(crate) struct FieldsToRetainFromNak {
803 pub(crate) server_identifier: net_types::SpecifiedAddr<net_types::ip::Ipv4Addr>,
804 pub(crate) message: Option<String>,
805 pub(crate) client_identifier: Option<
806 AtLeast<
807 { dhcp_protocol::CLIENT_IDENTIFIER_MINIMUM_LENGTH },
808 AtMostBytes<{ dhcp_protocol::U8_MAX_AS_USIZE }, Vec<u8>>,
809 >,
810 >,
811}
812
813pub(crate) fn fields_to_retain_from_response_to_request(
814 requested_parameters: &OptionCodeMap<OptionRequested>,
815 message: dhcp_protocol::Message,
816) -> Result<
817 IncomingResponseToRequest<
818 Option<net_types::SpecifiedAddr<net_types::ip::Ipv4Addr>>,
824 >,
825 IncomingResponseToRequestError,
826> {
827 let CommonIncomingMessageFields {
828 message_type,
829 server_identifier,
830 yiaddr,
831 ip_address_lease_time_secs,
832 renewal_time_value_secs,
833 rebinding_time_value_secs,
834 parameters,
835 seen_option_codes,
836 message,
837 client_identifier,
838 } = collect_common_fields(requested_parameters, message)?;
839
840 match message_type {
841 dhcp_protocol::MessageType::DHCPACK => {
842 if let Some(missing_option_code) =
846 requested_parameters.iter_required().find(|code| !seen_option_codes.contains(*code))
847 {
848 return Err(IncomingResponseToRequestError::MissingRequiredOption(
849 missing_option_code,
850 ));
851 }
852 Ok(IncomingResponseToRequest::Ack(FieldsToRetainFromAck {
853 yiaddr: yiaddr.ok_or(IncomingResponseToRequestError::UnspecifiedYiaddr)?,
854 server_identifier,
855 ip_address_lease_time_secs: ip_address_lease_time_secs
856 .ok_or(IncomingResponseToRequestError::NoLeaseTime)?,
857 renewal_time_value_secs,
858 rebinding_time_value_secs,
859 parameters,
860 }))
861 }
862 dhcp_protocol::MessageType::DHCPNAK => {
863 Ok(IncomingResponseToRequest::Nak(FieldsToRetainFromNak {
864 server_identifier: server_identifier
865 .ok_or(IncomingResponseToRequestError::NoServerIdentifier)?,
866 message,
867 client_identifier,
868 }))
869 }
870 dhcp_protocol::MessageType::DHCPDISCOVER
871 | dhcp_protocol::MessageType::DHCPOFFER
872 | dhcp_protocol::MessageType::DHCPREQUEST
873 | dhcp_protocol::MessageType::DHCPDECLINE
874 | dhcp_protocol::MessageType::DHCPRELEASE
875 | dhcp_protocol::MessageType::DHCPINFORM => {
876 Err(IncomingResponseToRequestError::NotDhcpAckOrNak(message_type))
877 }
878 }
879}
880
881#[cfg(test)]
882mod test {
883 use super::*;
884 use assert_matches::assert_matches;
885 use dhcp_protocol::{CLIENT_PORT, SERVER_PORT};
886 use net_declare::net::prefix_length_v4;
887 use net_declare::{net_ip_v4, net_mac, std_ip_v4};
888 use net_types::ip::{Ip, Ipv4, PrefixLength};
889 use std::net::Ipv4Addr;
890 use test_case::test_case;
891
892 #[test]
893 fn serialize_parse_roundtrip() {
894 let make_message = || dhcp_protocol::Message {
895 op: dhcp_protocol::OpCode::BOOTREQUEST,
896 xid: 124,
897 secs: 99,
898 bdcast_flag: false,
899 ciaddr: net_ip_v4!("1.2.3.4").into(),
900 yiaddr: net_ip_v4!("5.6.7.8").into(),
901 siaddr: net_ip_v4!("9.10.11.12").into(),
902 giaddr: net_ip_v4!("13.14.15.16").into(),
903 chaddr: net_mac!("17:18:19:20:21:22"),
904 sname: "this is a sname".to_owned(),
905 file: "this is the boot filename".to_owned(),
906 options: vec![
907 dhcp_protocol::DhcpOption::DhcpMessageType(
908 dhcp_protocol::MessageType::DHCPDISCOVER,
909 ),
910 dhcp_protocol::DhcpOption::RequestedIpAddress(net_ip_v4!("5.6.7.8").into()),
911 ],
912 };
913 let packet = serialize_dhcp_message_to_ip_packet(
914 make_message(),
915 Ipv4Addr::UNSPECIFIED,
916 CLIENT_PORT,
917 Ipv4Addr::BROADCAST,
918 SERVER_PORT,
919 );
920 let (src_addr, parsed_message) =
921 parse_dhcp_message_from_ip_packet(packet.as_ref(), SERVER_PORT).unwrap();
922
923 assert_eq!(net_types::ip::Ipv4::UNSPECIFIED_ADDRESS, src_addr);
924 assert_eq!(make_message(), parsed_message);
925 }
926
927 #[test]
928 fn nonsense() {
929 assert_matches!(
930 parse_dhcp_message_from_ip_packet(
931 &[0xD, 0xE, 0xA, 0xD, 0xB, 0xE, 0xE, 0xF],
932 NonZeroU16::new(1).unwrap()
933 ),
934 Err(ParseError::Ipv4(parse_error)) => {
935 assert_eq!(parse_error, packet_formats::error::IpParseError::Parse { error: packet_formats::error::ParseError::Format })
936 }
937 )
938 }
939
940 #[test]
941 fn not_udp() {
942 let src_ip = Ipv4Addr::UNSPECIFIED.into();
943 let dst_ip = Ipv4Addr::BROADCAST.into();
944 let tcp_builder: packet_formats::tcp::TcpSegmentBuilder<net_types::ip::Ipv4Addr> =
945 packet_formats::tcp::TcpSegmentBuilder::new(
946 src_ip,
947 dst_ip,
948 CLIENT_PORT,
949 SERVER_PORT,
950 0,
951 None,
952 0,
953 );
954 let ipv4_builder = packet_formats::ipv4::Ipv4PacketBuilder::new(
955 src_ip,
956 dst_ip,
957 DEFAULT_TTL,
958 packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Tcp),
959 );
960 let bytes = vec![1, 2, 3, 4, 5]
961 .into_serializer()
962 .wrap_in(tcp_builder)
963 .wrap_in(ipv4_builder)
964 .serialize_vec_outer()
965 .expect("serialize error");
966
967 assert_matches!(
968 parse_dhcp_message_from_ip_packet(bytes.as_ref(), NonZeroU16::new(1).unwrap()),
969 Err(ParseError::NotUdp)
970 );
971 }
972
973 #[test]
974 fn wrong_port() {
975 let src_ip = Ipv4Addr::UNSPECIFIED.into();
976 let dst_ip = Ipv4Addr::BROADCAST.into();
977
978 let udp_builder: packet_formats::udp::UdpPacketBuilder<net_types::ip::Ipv4Addr> =
979 packet_formats::udp::UdpPacketBuilder::new(
980 src_ip,
981 dst_ip,
982 Some(CLIENT_PORT),
983 SERVER_PORT,
984 );
985 let ipv4_builder = packet_formats::ipv4::Ipv4PacketBuilder::new(
986 src_ip,
987 dst_ip,
988 DEFAULT_TTL,
989 packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Udp),
990 );
991
992 let bytes = "hello_world"
993 .bytes()
994 .collect::<Vec<_>>()
995 .into_serializer()
996 .wrap_in(udp_builder)
997 .wrap_in(ipv4_builder)
998 .serialize_vec_outer()
999 .expect("serialize error");
1000
1001 let result = parse_dhcp_message_from_ip_packet(bytes.as_ref(), CLIENT_PORT);
1002 assert_matches!(result, Err(ParseError::WrongPort(port)) => assert_eq!(port, SERVER_PORT));
1003 }
1004
1005 struct VaryingOfferFields {
1006 op: dhcp_protocol::OpCode,
1007 yiaddr: Ipv4Addr,
1008 message_type: Option<dhcp_protocol::MessageType>,
1009 server_identifier: Option<Ipv4Addr>,
1010 subnet_mask: Option<PrefixLength<Ipv4>>,
1011 lease_length_secs: Option<u32>,
1012 include_duplicate_option: bool,
1013 }
1014
1015 const SERVER_IP: Ipv4Addr = std_ip_v4!("192.168.1.1");
1016 const TEST_SUBNET_MASK: PrefixLength<Ipv4> = prefix_length_v4!(24);
1017 const LEASE_LENGTH_SECS: u32 = 100;
1018 const LEASE_LENGTH_SECS_NONZERO: NonZeroU32 = NonZeroU32::new(LEASE_LENGTH_SECS).unwrap();
1019 const YIADDR: Ipv4Addr = std_ip_v4!("192.168.1.5");
1020
1021 #[test_case(VaryingOfferFields {
1022 op: dhcp_protocol::OpCode::BOOTREPLY,
1023 yiaddr: YIADDR,
1024 message_type: Some(dhcp_protocol::MessageType::DHCPOFFER),
1025 server_identifier: Some(SERVER_IP),
1026 subnet_mask: Some(TEST_SUBNET_MASK),
1027 lease_length_secs: Some(LEASE_LENGTH_SECS),
1028 include_duplicate_option: false,
1029 } => Ok(FieldsFromOfferToUseInRequest {
1030 server_identifier: net_types::ip::Ipv4Addr::from(SERVER_IP)
1031 .try_into()
1032 .expect("should be specified"),
1033 ip_address_lease_time_secs: Some(LEASE_LENGTH_SECS_NONZERO),
1034 ip_address_to_request: net_types::ip::Ipv4Addr::from(YIADDR)
1035 .try_into()
1036 .expect("should be specified"),
1037 }); "accepts good offer with lease time")]
1038 #[test_case(VaryingOfferFields {
1039 op: dhcp_protocol::OpCode::BOOTREPLY,
1040 yiaddr: YIADDR,
1041 message_type: Some(dhcp_protocol::MessageType::DHCPOFFER),
1042 server_identifier: Some(SERVER_IP),
1043 subnet_mask: Some(TEST_SUBNET_MASK),
1044 lease_length_secs: None,
1045 include_duplicate_option: false,
1046 } => Ok(FieldsFromOfferToUseInRequest {
1047 server_identifier: net_types::ip::Ipv4Addr::from(SERVER_IP)
1048 .try_into()
1049 .expect("should be specified"),
1050 ip_address_lease_time_secs: None,
1051 ip_address_to_request: net_types::ip::Ipv4Addr::from(YIADDR)
1052 .try_into()
1053 .expect("should be specified"),
1054 }); "accepts good offer without lease time")]
1055 #[test_case(VaryingOfferFields {
1056 op: dhcp_protocol::OpCode::BOOTREPLY,
1057 yiaddr: YIADDR,
1058 message_type: Some(dhcp_protocol::MessageType::DHCPOFFER),
1059 server_identifier: Some(Ipv4Addr::UNSPECIFIED),
1060 subnet_mask: Some(TEST_SUBNET_MASK),
1061 lease_length_secs: Some(LEASE_LENGTH_SECS),
1062 include_duplicate_option: false,
1063 } => Err(SelectingIncomingMessageError::CommonError(
1064 CommonIncomingMessageError::UnspecifiedServerIdentifier,
1065 )); "rejects offer with unspecified server identifier")]
1066 #[test_case(VaryingOfferFields {
1067 op: dhcp_protocol::OpCode::BOOTREPLY,
1068 yiaddr: YIADDR,
1069 message_type: Some(dhcp_protocol::MessageType::DHCPOFFER),
1070 server_identifier: Some(SERVER_IP),
1071 subnet_mask: None,
1072 lease_length_secs: Some(LEASE_LENGTH_SECS),
1073 include_duplicate_option: false,
1074 } => Err(SelectingIncomingMessageError::MissingRequiredOption(
1075 dhcp_protocol::OptionCode::SubnetMask,
1076 )); "rejects offer without required subnet mask")]
1077 #[test_case(VaryingOfferFields {
1078 op: dhcp_protocol::OpCode::BOOTREPLY,
1079 yiaddr: YIADDR,
1080 message_type: Some(dhcp_protocol::MessageType::DHCPOFFER),
1081 server_identifier: None,
1082 subnet_mask: Some(TEST_SUBNET_MASK),
1083 lease_length_secs: Some(LEASE_LENGTH_SECS),
1084 include_duplicate_option: false,
1085 } => Err(SelectingIncomingMessageError::NoServerIdentifier); "rejects offer with no server identifier option")]
1086 #[test_case(VaryingOfferFields {
1087 op: dhcp_protocol::OpCode::BOOTREPLY,
1088 yiaddr: Ipv4Addr::UNSPECIFIED,
1089 message_type: Some(dhcp_protocol::MessageType::DHCPOFFER),
1090 server_identifier: Some(SERVER_IP),
1091 subnet_mask: Some(TEST_SUBNET_MASK),
1092 lease_length_secs: Some(LEASE_LENGTH_SECS),
1093 include_duplicate_option: false,
1094 } => Err(SelectingIncomingMessageError::UnspecifiedYiaddr) ; "rejects offer with unspecified yiaddr")]
1095 #[test_case(VaryingOfferFields {
1096 op: dhcp_protocol::OpCode::BOOTREQUEST,
1097 yiaddr: YIADDR,
1098 message_type: Some(dhcp_protocol::MessageType::DHCPOFFER),
1099 server_identifier: Some(SERVER_IP),
1100 subnet_mask: Some(TEST_SUBNET_MASK),
1101 lease_length_secs: Some(LEASE_LENGTH_SECS),
1102 include_duplicate_option: false,
1103 } => Err(SelectingIncomingMessageError::CommonError(
1104 CommonIncomingMessageError::NotBootReply(dhcp_protocol::OpCode::BOOTREQUEST),
1105 )); "rejects offer that isn't a bootreply")]
1106 #[test_case(VaryingOfferFields {
1107 op: dhcp_protocol::OpCode::BOOTREPLY,
1108 yiaddr: YIADDR,
1109 message_type: Some(dhcp_protocol::MessageType::DHCPACK),
1110 server_identifier: Some(SERVER_IP),
1111 subnet_mask: Some(TEST_SUBNET_MASK),
1112 lease_length_secs: Some(LEASE_LENGTH_SECS),
1113 include_duplicate_option: false,
1114 } => Err(
1115 SelectingIncomingMessageError::NotDhcpOffer(dhcp_protocol::MessageType::DHCPACK),
1116 ); "rejects offer with wrong DHCP message type")]
1117 #[test_case(VaryingOfferFields {
1118 op: dhcp_protocol::OpCode::BOOTREPLY,
1119 yiaddr: YIADDR,
1120 message_type: None,
1121 server_identifier: Some(SERVER_IP),
1122 subnet_mask: Some(TEST_SUBNET_MASK),
1123 lease_length_secs: Some(LEASE_LENGTH_SECS),
1124 include_duplicate_option: false,
1125 } => Err(SelectingIncomingMessageError::CommonError(
1126 CommonIncomingMessageError::BuilderMissingField("message_type"),
1127 )); "rejects offer with no DHCP message type option")]
1128 #[test_case(VaryingOfferFields {
1129 op: dhcp_protocol::OpCode::BOOTREPLY,
1130 yiaddr: YIADDR,
1131 message_type: Some(dhcp_protocol::MessageType::DHCPOFFER),
1132 server_identifier: Some(SERVER_IP),
1133 subnet_mask: Some(TEST_SUBNET_MASK),
1134 lease_length_secs: Some(LEASE_LENGTH_SECS),
1135 include_duplicate_option: true,
1136 } => Err(SelectingIncomingMessageError::CommonError(
1137 CommonIncomingMessageError::DuplicateOption(
1138 dhcp_protocol::OptionCode::DomainName,
1139 ),
1140 )); "rejects offer with duplicate DHCP option")]
1141 fn fields_from_offer_to_use_in_request(
1142 offer_fields: VaryingOfferFields,
1143 ) -> Result<FieldsFromOfferToUseInRequest, SelectingIncomingMessageError> {
1144 use super::fields_to_retain_from_selecting as fields;
1145 use dhcp_protocol::DhcpOption;
1146
1147 let VaryingOfferFields {
1148 op,
1149 yiaddr,
1150 message_type,
1151 server_identifier,
1152 subnet_mask,
1153 lease_length_secs,
1154 include_duplicate_option,
1155 } = offer_fields;
1156
1157 let message = dhcp_protocol::Message {
1158 op,
1159 xid: 1,
1160 secs: 0,
1161 bdcast_flag: false,
1162 ciaddr: Ipv4Addr::UNSPECIFIED,
1163 yiaddr,
1164 siaddr: Ipv4Addr::UNSPECIFIED,
1165 giaddr: Ipv4Addr::UNSPECIFIED,
1166 chaddr: net_mac!("01:02:03:04:05:06"),
1167 sname: String::new(),
1168 file: String::new(),
1169 options: message_type
1170 .map(DhcpOption::DhcpMessageType)
1171 .into_iter()
1172 .chain(server_identifier.map(DhcpOption::ServerIdentifier))
1173 .chain(subnet_mask.map(DhcpOption::SubnetMask))
1174 .chain(lease_length_secs.map(DhcpOption::IpAddressLeaseTime))
1175 .chain(
1176 include_duplicate_option
1177 .then_some([
1178 dhcp_protocol::DhcpOption::DomainName("example.com".to_owned()),
1179 dhcp_protocol::DhcpOption::DomainName("example.com".to_owned()),
1180 ])
1181 .into_iter()
1182 .flatten(),
1183 )
1184 .collect(),
1185 };
1186
1187 fields(
1188 &std::iter::once((dhcp_protocol::OptionCode::SubnetMask, OptionRequested::Required))
1189 .collect(),
1190 message,
1191 )
1192 }
1193
1194 struct VaryingReplyToRequestFields {
1195 op: dhcp_protocol::OpCode,
1196 yiaddr: Ipv4Addr,
1197 message_type: Option<dhcp_protocol::MessageType>,
1198 server_identifier: Option<Ipv4Addr>,
1199 subnet_mask: Option<PrefixLength<Ipv4>>,
1200 lease_length_secs: Option<u32>,
1201 renewal_time_secs: Option<u32>,
1202 rebinding_time_secs: Option<u32>,
1203 message: Option<String>,
1204 include_duplicate_option: bool,
1205 }
1206
1207 const DOMAIN_NAME: &str = "example.com";
1208 const MESSAGE: &str = "message explaining why the DHCPNAK was sent";
1209 const RENEWAL_TIME_SECS: u32 = LEASE_LENGTH_SECS / 2;
1210 const REBINDING_TIME_SECS: u32 = LEASE_LENGTH_SECS * 3 / 4;
1211
1212 #[test_case(
1213 VaryingReplyToRequestFields {
1214 op: dhcp_protocol::OpCode::BOOTREPLY,
1215 yiaddr: YIADDR,
1216 message_type: Some(dhcp_protocol::MessageType::DHCPACK),
1217 server_identifier: Some(SERVER_IP),
1218 subnet_mask: Some(TEST_SUBNET_MASK),
1219 lease_length_secs: Some(LEASE_LENGTH_SECS),
1220 renewal_time_secs: None,
1221 rebinding_time_secs: None,
1222 message: None,
1223 include_duplicate_option: false,
1224 } => Ok(IncomingResponseToRequest::Ack(FieldsToRetainFromAck {
1225 yiaddr: net_types::ip::Ipv4Addr::from(YIADDR)
1226 .try_into()
1227 .expect("should be specified"),
1228 server_identifier: Some(
1229 net_types::ip::Ipv4Addr::from(SERVER_IP)
1230 .try_into()
1231 .expect("should be specified"),
1232 ),
1233 ip_address_lease_time_secs: LEASE_LENGTH_SECS_NONZERO,
1234 parameters: vec![
1235 dhcp_protocol::DhcpOption::SubnetMask(TEST_SUBNET_MASK),
1236 dhcp_protocol::DhcpOption::DomainName(DOMAIN_NAME.to_owned())
1237 ],
1238 renewal_time_value_secs: None,
1239 rebinding_time_value_secs: None,
1240 })); "accepts good DHCPACK")]
1241 #[test_case(VaryingReplyToRequestFields {
1242 op: dhcp_protocol::OpCode::BOOTREPLY,
1243 yiaddr: YIADDR,
1244 message_type: Some(dhcp_protocol::MessageType::DHCPACK),
1245 server_identifier: None,
1246 subnet_mask: Some(TEST_SUBNET_MASK),
1247 lease_length_secs: Some(LEASE_LENGTH_SECS),
1248 renewal_time_secs: None,
1249 rebinding_time_secs: None,
1250 message: None,
1251 include_duplicate_option: false,
1252 } => Ok(IncomingResponseToRequest::Ack(FieldsToRetainFromAck {
1253 yiaddr: net_types::ip::Ipv4Addr::from(YIADDR)
1254 .try_into()
1255 .expect("should be specified"),
1256 server_identifier: None,
1257 ip_address_lease_time_secs: LEASE_LENGTH_SECS_NONZERO,
1258 parameters: vec![
1259 dhcp_protocol::DhcpOption::SubnetMask(TEST_SUBNET_MASK),
1260 dhcp_protocol::DhcpOption::DomainName(DOMAIN_NAME.to_owned())
1261 ],
1262 renewal_time_value_secs: None,
1263 rebinding_time_value_secs: None,
1264 })); "accepts DHCPACK with no server identifier")]
1265 #[test_case(VaryingReplyToRequestFields {
1266 op: dhcp_protocol::OpCode::BOOTREPLY,
1267 yiaddr: YIADDR,
1268 message_type: Some(dhcp_protocol::MessageType::DHCPACK),
1269 server_identifier: Some(SERVER_IP),
1270 subnet_mask: Some(TEST_SUBNET_MASK),
1271 lease_length_secs: Some(LEASE_LENGTH_SECS),
1272 renewal_time_secs: Some(RENEWAL_TIME_SECS),
1273 rebinding_time_secs: Some(REBINDING_TIME_SECS),
1274 message: None,
1275 include_duplicate_option: false,
1276 } => Ok(IncomingResponseToRequest::Ack(FieldsToRetainFromAck {
1277 yiaddr: net_types::ip::Ipv4Addr::from(YIADDR)
1278 .try_into()
1279 .expect("should be specified"),
1280 server_identifier: Some(
1281 net_types::ip::Ipv4Addr::from(SERVER_IP)
1282 .try_into()
1283 .expect("should be specified"),
1284 ),
1285 ip_address_lease_time_secs: LEASE_LENGTH_SECS_NONZERO,
1286 parameters: vec![
1287 dhcp_protocol::DhcpOption::SubnetMask(TEST_SUBNET_MASK),
1288 dhcp_protocol::DhcpOption::DomainName(DOMAIN_NAME.to_owned())
1289 ],
1290 renewal_time_value_secs: Some(RENEWAL_TIME_SECS),
1291 rebinding_time_value_secs: Some(REBINDING_TIME_SECS),
1292 })); "accepts DHCPACK with renew and rebind times")]
1293 #[test_case(VaryingReplyToRequestFields {
1294 op: dhcp_protocol::OpCode::BOOTREPLY,
1295 yiaddr: Ipv4Addr::UNSPECIFIED,
1296 message_type: Some(dhcp_protocol::MessageType::DHCPNAK),
1297 server_identifier: Some(SERVER_IP),
1298 subnet_mask: None,
1299 lease_length_secs: None,
1300 renewal_time_secs: None,
1301 rebinding_time_secs: None,
1302 message: Some(MESSAGE.to_owned()),
1303 include_duplicate_option: false,
1304 } => Ok(IncomingResponseToRequest::Nak(FieldsToRetainFromNak {
1305 server_identifier: net_types::ip::Ipv4Addr::from(SERVER_IP)
1306 .try_into()
1307 .expect("should be specified"),
1308 message: Some(MESSAGE.to_owned()),
1309 client_identifier: None,
1310 })); "accepts good DHCPNAK")]
1311 #[test_case(VaryingReplyToRequestFields {
1312 op: dhcp_protocol::OpCode::BOOTREPLY,
1313 yiaddr: YIADDR,
1314 message_type: Some(dhcp_protocol::MessageType::DHCPACK),
1315 server_identifier: Some(SERVER_IP),
1316 subnet_mask: Some(TEST_SUBNET_MASK),
1317 lease_length_secs: None,
1318 renewal_time_secs: Some(RENEWAL_TIME_SECS),
1319 rebinding_time_secs: Some(REBINDING_TIME_SECS),
1320 message: None,
1321 include_duplicate_option: false,
1322 } => Err(IncomingResponseToRequestError::NoLeaseTime); "rejects DHCPACK with no lease time")]
1323 #[test_case(
1324 VaryingReplyToRequestFields {
1325 op: dhcp_protocol::OpCode::BOOTREPLY,
1326 yiaddr: YIADDR,
1327 message_type: Some(dhcp_protocol::MessageType::DHCPACK),
1328 server_identifier: Some(SERVER_IP),
1329 subnet_mask: None,
1330 lease_length_secs: Some(LEASE_LENGTH_SECS),
1331 renewal_time_secs: None,
1332 rebinding_time_secs: None,
1333 message: None,
1334 include_duplicate_option: false,
1335 } => Err(IncomingResponseToRequestError::MissingRequiredOption(
1336 dhcp_protocol::OptionCode::SubnetMask
1337 )); "rejects DHCPACK without required subnet mask")]
1338 #[test_case(VaryingReplyToRequestFields {
1339 op: dhcp_protocol::OpCode::BOOTREPLY,
1340 yiaddr: YIADDR,
1341 message_type: Some(dhcp_protocol::MessageType::DHCPACK),
1342 server_identifier: Some(Ipv4Addr::UNSPECIFIED),
1343 subnet_mask: Some(TEST_SUBNET_MASK),
1344 lease_length_secs: Some(LEASE_LENGTH_SECS),
1345 renewal_time_secs: Some(RENEWAL_TIME_SECS),
1346 rebinding_time_secs: Some(REBINDING_TIME_SECS),
1347 message: None,
1348 include_duplicate_option: false,
1349 } => Err(IncomingResponseToRequestError::CommonError(
1350 CommonIncomingMessageError::UnspecifiedServerIdentifier,
1351 )); "rejects DHCPACK with unspecified server identifier")]
1352 #[test_case(VaryingReplyToRequestFields {
1353 op: dhcp_protocol::OpCode::BOOTREPLY,
1354 yiaddr: Ipv4Addr::UNSPECIFIED,
1355 message_type: Some(dhcp_protocol::MessageType::DHCPACK),
1356 server_identifier: Some(SERVER_IP),
1357 subnet_mask: Some(TEST_SUBNET_MASK),
1358 lease_length_secs: Some(LEASE_LENGTH_SECS),
1359 renewal_time_secs: Some(RENEWAL_TIME_SECS),
1360 rebinding_time_secs: Some(REBINDING_TIME_SECS),
1361 message: None,
1362 include_duplicate_option: false,
1363 } => Err(IncomingResponseToRequestError::UnspecifiedYiaddr); "rejects DHCPACK with unspecified yiaddr")]
1364 #[test_case(VaryingReplyToRequestFields {
1365 op: dhcp_protocol::OpCode::BOOTREPLY,
1366 yiaddr: Ipv4Addr::UNSPECIFIED,
1367 message_type: Some(dhcp_protocol::MessageType::DHCPNAK),
1368 server_identifier: Some(Ipv4Addr::UNSPECIFIED),
1369 subnet_mask: None,
1370 lease_length_secs: None,
1371 renewal_time_secs: None,
1372 rebinding_time_secs: None,
1373 message: Some(MESSAGE.to_owned()),
1374 include_duplicate_option: false,
1375 } => Err(IncomingResponseToRequestError::CommonError(
1376 CommonIncomingMessageError::UnspecifiedServerIdentifier,
1377 )); "rejects DHCPNAK with unspecified server identifier")]
1378 #[test_case(VaryingReplyToRequestFields {
1379 op: dhcp_protocol::OpCode::BOOTREPLY,
1380 yiaddr: Ipv4Addr::UNSPECIFIED,
1381 message_type: Some(dhcp_protocol::MessageType::DHCPNAK),
1382 server_identifier: None,
1383 subnet_mask: None,
1384 lease_length_secs: None,
1385 renewal_time_secs: None,
1386 rebinding_time_secs: None,
1387 message: Some(MESSAGE.to_owned()),
1388 include_duplicate_option: false,
1389 } => Err(IncomingResponseToRequestError::NoServerIdentifier) ; "rejects DHCPNAK with no server identifier")]
1390 #[test_case(VaryingReplyToRequestFields {
1391 op: dhcp_protocol::OpCode::BOOTREQUEST,
1392 yiaddr: Ipv4Addr::UNSPECIFIED,
1393 message_type: Some(dhcp_protocol::MessageType::DHCPNAK),
1394 server_identifier: Some(SERVER_IP),
1395 subnet_mask: None,
1396 lease_length_secs: None,
1397 renewal_time_secs: None,
1398 rebinding_time_secs: None,
1399 message: Some(MESSAGE.to_owned()),
1400 include_duplicate_option: false,
1401 } => Err(IncomingResponseToRequestError::CommonError(
1402 CommonIncomingMessageError::NotBootReply(dhcp_protocol::OpCode::BOOTREQUEST),
1403 )) ; "rejects non-bootreply")]
1404 #[test_case(VaryingReplyToRequestFields {
1405 op: dhcp_protocol::OpCode::BOOTREPLY,
1406 yiaddr: Ipv4Addr::UNSPECIFIED,
1407 message_type: Some(dhcp_protocol::MessageType::DHCPOFFER),
1408 server_identifier: Some(SERVER_IP),
1409 subnet_mask: Some(TEST_SUBNET_MASK),
1410 lease_length_secs: None,
1411 renewal_time_secs: None,
1412 rebinding_time_secs: None,
1413 message: Some(MESSAGE.to_owned()),
1414 include_duplicate_option: false,
1415 } => Err(IncomingResponseToRequestError::NotDhcpAckOrNak(
1416 dhcp_protocol::MessageType::DHCPOFFER,
1417 )) ; "rejects non-DHCPACK or DHCPNAK")]
1418 #[test_case(VaryingReplyToRequestFields {
1419 op: dhcp_protocol::OpCode::BOOTREPLY,
1420 yiaddr: Ipv4Addr::UNSPECIFIED,
1421 message_type: None,
1422 server_identifier: Some(SERVER_IP),
1423 subnet_mask: None,
1424 lease_length_secs: None,
1425 renewal_time_secs: None,
1426 rebinding_time_secs: None,
1427 message: Some(MESSAGE.to_owned()),
1428 include_duplicate_option: false,
1429 } => Err(IncomingResponseToRequestError::CommonError(
1430 CommonIncomingMessageError::BuilderMissingField("message_type"),
1431 )) ; "rejects missing DHCP message type")]
1432 #[test_case( VaryingReplyToRequestFields {
1433 op: dhcp_protocol::OpCode::BOOTREPLY,
1434 yiaddr: YIADDR,
1435 message_type: Some(dhcp_protocol::MessageType::DHCPACK),
1436 server_identifier: Some(SERVER_IP),
1437 subnet_mask: Some(TEST_SUBNET_MASK),
1438 lease_length_secs: Some(LEASE_LENGTH_SECS),
1439 renewal_time_secs: Some(RENEWAL_TIME_SECS),
1440 rebinding_time_secs: Some(REBINDING_TIME_SECS),
1441 message: None,
1442 include_duplicate_option: true,
1443 } => Err(IncomingResponseToRequestError::CommonError(
1444 CommonIncomingMessageError::DuplicateOption(
1445 dhcp_protocol::OptionCode::DomainName,
1446 ),
1447 )); "rejects duplicate option")]
1448 fn fields_to_retain_during_requesting(
1449 incoming_fields: VaryingReplyToRequestFields,
1450 ) -> Result<
1451 IncomingResponseToRequest<Option<net_types::SpecifiedAddr<net_types::ip::Ipv4Addr>>>,
1452 IncomingResponseToRequestError,
1453 > {
1454 use super::fields_to_retain_from_response_to_request as fields;
1455 use dhcp_protocol::DhcpOption;
1456
1457 let VaryingReplyToRequestFields {
1458 op,
1459 yiaddr,
1460 message_type,
1461 server_identifier,
1462 subnet_mask,
1463 lease_length_secs,
1464 renewal_time_secs,
1465 rebinding_time_secs,
1466 message,
1467 include_duplicate_option,
1468 } = incoming_fields;
1469
1470 let message = dhcp_protocol::Message {
1471 op,
1472 xid: 1,
1473 secs: 0,
1474 bdcast_flag: false,
1475 ciaddr: Ipv4Addr::UNSPECIFIED,
1476 yiaddr,
1477 siaddr: Ipv4Addr::UNSPECIFIED,
1478 giaddr: Ipv4Addr::UNSPECIFIED,
1479 chaddr: net_mac!("01:02:03:04:05:06"),
1480 sname: String::new(),
1481 file: String::new(),
1482 options: std::iter::empty()
1483 .chain(message_type.map(DhcpOption::DhcpMessageType))
1484 .chain(server_identifier.map(DhcpOption::ServerIdentifier))
1485 .chain(subnet_mask.map(DhcpOption::SubnetMask))
1486 .chain(lease_length_secs.map(DhcpOption::IpAddressLeaseTime))
1487 .chain(renewal_time_secs.map(DhcpOption::RenewalTimeValue))
1488 .chain(rebinding_time_secs.map(DhcpOption::RebindingTimeValue))
1489 .chain(message.map(DhcpOption::Message))
1490 .chain(std::iter::once(dhcp_protocol::DhcpOption::InterfaceMtu(1)))
1493 .chain(std::iter::once(dhcp_protocol::DhcpOption::DomainName(
1496 DOMAIN_NAME.to_owned(),
1497 )))
1498 .chain(
1499 include_duplicate_option
1500 .then_some(dhcp_protocol::DhcpOption::DomainName(DOMAIN_NAME.to_owned())),
1501 )
1502 .collect(),
1503 };
1504
1505 fields(
1506 &[
1507 (dhcp_protocol::OptionCode::SubnetMask, OptionRequested::Required),
1508 (dhcp_protocol::OptionCode::DomainName, OptionRequested::Optional),
1509 ]
1510 .into_iter()
1511 .collect(),
1512 message,
1513 )
1514 }
1515}