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