1use core::fmt;
72use std::fmt::{Display, Formatter};
73use std::net::Ipv4Addr;
74use std::ops::Deref;
75use zerocopy::byteorder::{BigEndian, U16, U32};
76use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
77
78use crate::packet::ether::EthAddr;
79use crate::packet::{HeaderParser, PacketHeader};
80
81pub const DHCP_MAGIC_COOKIE: u32 = 0x63825363;
83
84pub const DHCP_SERVER_PORT: u16 = 67;
86
87pub const DHCP_CLIENT_PORT: u16 = 68;
89
90pub const DHCP_MAX_PACKET_SIZE: usize = 576;
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
95#[repr(u8)]
96pub enum DhcpOpCode {
97 BOOTREQUEST = 1, BOOTREPLY = 2, }
100
101impl DhcpOpCode {
102 pub fn from_u8(value: u8) -> Option<Self> {
103 match value {
104 1 => Some(DhcpOpCode::BOOTREQUEST),
105 2 => Some(DhcpOpCode::BOOTREPLY),
106 _ => None,
107 }
108 }
109}
110
111impl Display for DhcpOpCode {
112 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
113 match self {
114 DhcpOpCode::BOOTREQUEST => write!(f, "BOOTREQUEST"),
115 DhcpOpCode::BOOTREPLY => write!(f, "BOOTREPLY"),
116 }
117 }
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
122#[repr(u8)]
123pub enum DhcpHardwareType {
124 ETHERNET = 1,
125 IEEE802 = 6,
126}
127
128impl DhcpHardwareType {
129 pub fn from_u8(value: u8) -> Option<Self> {
130 match value {
131 1 => Some(DhcpHardwareType::ETHERNET),
132 6 => Some(DhcpHardwareType::IEEE802),
133 _ => None,
134 }
135 }
136}
137
138impl Display for DhcpHardwareType {
139 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
140 match self {
141 DhcpHardwareType::ETHERNET => write!(f, "ethernet"),
142 DhcpHardwareType::IEEE802 => write!(f, "ieee802"),
143 }
144 }
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
149#[repr(u8)]
150pub enum DhcpMessageType {
151 DISCOVER = 1,
152 OFFER = 2,
153 REQUEST = 3,
154 DECLINE = 4,
155 ACK = 5,
156 NAK = 6,
157 RELEASE = 7,
158 INFORM = 8,
159}
160
161impl DhcpMessageType {
162 pub fn from_u8(value: u8) -> Option<Self> {
163 match value {
164 1 => Some(DhcpMessageType::DISCOVER),
165 2 => Some(DhcpMessageType::OFFER),
166 3 => Some(DhcpMessageType::REQUEST),
167 4 => Some(DhcpMessageType::DECLINE),
168 5 => Some(DhcpMessageType::ACK),
169 6 => Some(DhcpMessageType::NAK),
170 7 => Some(DhcpMessageType::RELEASE),
171 8 => Some(DhcpMessageType::INFORM),
172 _ => None,
173 }
174 }
175}
176
177impl Display for DhcpMessageType {
178 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
179 match self {
180 DhcpMessageType::DISCOVER => write!(f, "DISCOVER"),
181 DhcpMessageType::OFFER => write!(f, "OFFER"),
182 DhcpMessageType::REQUEST => write!(f, "REQUEST"),
183 DhcpMessageType::DECLINE => write!(f, "DECLINE"),
184 DhcpMessageType::ACK => write!(f, "ACK"),
185 DhcpMessageType::NAK => write!(f, "NAK"),
186 DhcpMessageType::RELEASE => write!(f, "RELEASE"),
187 DhcpMessageType::INFORM => write!(f, "INFORM"),
188 }
189 }
190}
191
192#[repr(C, packed)]
197#[derive(FromBytes, IntoBytes, Unaligned, Immutable, KnownLayout, Debug, Clone, Copy)]
198pub struct DhcpHeader {
199 op: u8, htype: u8, hlen: u8, hops: u8, xid: U32<BigEndian>, secs: U16<BigEndian>, flags: U16<BigEndian>, ciaddr: U32<BigEndian>, yiaddr: U32<BigEndian>, siaddr: U32<BigEndian>, giaddr: U32<BigEndian>, chaddr: [u8; 16], sname: [u8; 64], file: [u8; 128], magic_cookie: U32<BigEndian>, }
215
216impl DhcpHeader {
217 #[inline]
219 pub fn op(&self) -> DhcpOpCode {
220 DhcpOpCode::from_u8(self.op).unwrap_or(DhcpOpCode::BOOTREQUEST)
221 }
222
223 #[inline]
225 pub fn htype(&self) -> u8 {
226 self.htype
227 }
228
229 #[inline]
231 pub fn hlen(&self) -> u8 {
232 self.hlen
233 }
234
235 #[inline]
237 pub fn hops(&self) -> u8 {
238 self.hops
239 }
240
241 #[inline]
243 pub fn xid(&self) -> u32 {
244 self.xid.get()
245 }
246
247 #[inline]
249 pub fn secs(&self) -> u16 {
250 self.secs.get()
251 }
252
253 #[inline]
255 pub fn flags(&self) -> u16 {
256 self.flags.get()
257 }
258
259 #[inline]
261 pub fn is_broadcast(&self) -> bool {
262 (self.flags.get() & 0x8000) != 0
263 }
264
265 #[inline]
267 pub fn ciaddr(&self) -> Ipv4Addr {
268 Ipv4Addr::from(self.ciaddr.get())
269 }
270
271 #[inline]
273 pub fn yiaddr(&self) -> Ipv4Addr {
274 Ipv4Addr::from(self.yiaddr.get())
275 }
276
277 #[inline]
279 pub fn siaddr(&self) -> Ipv4Addr {
280 Ipv4Addr::from(self.siaddr.get())
281 }
282
283 #[inline]
285 pub fn giaddr(&self) -> Ipv4Addr {
286 Ipv4Addr::from(self.giaddr.get())
287 }
288
289 #[inline]
291 pub fn chaddr_raw(&self) -> &[u8] {
292 &self.chaddr[..self.hlen as usize]
293 }
294
295 #[inline]
297 pub fn chaddr_eth(&self) -> Option<&EthAddr> {
298 if self.htype == 1 && self.hlen == 6 {
299 zerocopy::Ref::<_, EthAddr>::from_prefix(&self.chaddr[..6])
300 .ok()
301 .map(|(r, _)| zerocopy::Ref::into_ref(r))
302 } else {
303 None
304 }
305 }
306
307 #[inline]
309 pub fn sname(&self) -> &[u8] {
310 let end = self.sname.iter().position(|&b| b == 0).unwrap_or(64);
312 &self.sname[..end]
313 }
314
315 #[inline]
317 pub fn file(&self) -> &[u8] {
318 let end = self.file.iter().position(|&b| b == 0).unwrap_or(128);
320 &self.file[..end]
321 }
322
323 #[inline]
325 pub fn magic_cookie(&self) -> u32 {
326 self.magic_cookie.get()
327 }
328
329 #[inline]
331 fn is_valid(&self) -> bool {
332 if self.magic_cookie.get() != DHCP_MAGIC_COOKIE {
334 return false;
335 }
336
337 if self.op != 1 && self.op != 2 {
339 return false;
340 }
341
342 if self.hlen > 16 {
344 return false;
345 }
346
347 true
348 }
349}
350
351impl PacketHeader for DhcpHeader {
352 const NAME: &'static str = "DhcpHeader";
353 type InnerType = ();
354
355 #[inline]
356 fn inner_type(&self) -> Self::InnerType {}
357
358 #[inline]
359 fn is_valid(&self) -> bool {
360 self.is_valid()
361 }
362}
363
364impl HeaderParser for DhcpHeader {
365 type Output<'a> = DhcpHeaderOpt<'a>;
366
367 #[inline]
368 fn into_view<'a>(header: &'a Self, options: &'a [u8]) -> Self::Output<'a> {
369 DhcpHeaderOpt { header, options }
370 }
371
372 fn from_bytes<'a>(
374 buf: &'a [u8],
375 ) -> Result<(Self::Output<'a>, &'a [u8]), crate::packet::PacketHeaderError> {
376 let (header_ref, rest) = zerocopy::Ref::<_, Self>::from_prefix(buf)
378 .map_err(|_| crate::packet::PacketHeaderError::TooShort(Self::NAME))?;
379
380 let header = zerocopy::Ref::into_ref(header_ref);
381
382 if !header.is_valid() {
383 return Err(crate::packet::PacketHeaderError::Invalid(Self::NAME));
384 }
385
386 let view = Self::into_view(header, rest);
388
389 Ok((view, &[]))
391 }
392}
393
394pub struct DhcpHeaderOpt<'a> {
399 pub header: &'a DhcpHeader,
400 pub options: &'a [u8],
401}
402
403impl<'a> DhcpHeaderOpt<'a> {
404 pub fn options(&self) -> DhcpOptionsIter<'a> {
406 DhcpOptionsIter::new(self.options)
407 }
408
409 pub fn find_option(&self, code: u8) -> Option<DhcpOption<'a>> {
411 self.options().find(|opt| opt.code() == Some(code))
412 }
413
414 pub fn message_type(&self) -> Option<DhcpMessageType> {
416 self.find_option(53).and_then(|opt| {
417 if let DhcpOption::MessageType(mt) = opt {
418 Some(mt)
419 } else {
420 None
421 }
422 })
423 }
424
425 pub fn requested_ip(&self) -> Option<Ipv4Addr> {
427 self.find_option(50).and_then(|opt| {
428 if let DhcpOption::RequestedIpAddress(ip) = opt {
429 Some(ip)
430 } else {
431 None
432 }
433 })
434 }
435
436 pub fn subnet_mask(&self) -> Option<Ipv4Addr> {
438 self.find_option(1).and_then(|opt| {
439 if let DhcpOption::SubnetMask(mask) = opt {
440 Some(mask)
441 } else {
442 None
443 }
444 })
445 }
446
447 pub fn router(&self) -> Option<Vec<Ipv4Addr>> {
449 self.find_option(3).and_then(|opt| {
450 if let DhcpOption::Router(routers) = opt {
451 Some(routers)
452 } else {
453 None
454 }
455 })
456 }
457
458 pub fn dns_servers(&self) -> Option<Vec<Ipv4Addr>> {
460 self.find_option(6).and_then(|opt| {
461 if let DhcpOption::DomainNameServer(dns) = opt {
462 Some(dns)
463 } else {
464 None
465 }
466 })
467 }
468
469 pub fn lease_time(&self) -> Option<u32> {
471 self.find_option(51).and_then(|opt| {
472 if let DhcpOption::IpAddressLeaseTime(time) = opt {
473 Some(time)
474 } else {
475 None
476 }
477 })
478 }
479
480 pub fn server_identifier(&self) -> Option<Ipv4Addr> {
482 self.find_option(54).and_then(|opt| {
483 if let DhcpOption::ServerIdentifier(ip) = opt {
484 Some(ip)
485 } else {
486 None
487 }
488 })
489 }
490}
491
492impl Deref for DhcpHeaderOpt<'_> {
493 type Target = DhcpHeader;
494
495 #[inline]
496 fn deref(&self) -> &Self::Target {
497 self.header
498 }
499}
500
501impl Display for DhcpHeaderOpt<'_> {
502 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
503 write!(f, "DHCP {} xid=0x{:08x}", self.op(), self.xid())?;
504
505 if let Some(msg_type) = self.message_type() {
506 write!(f, " type={}", msg_type)?;
507 }
508
509 if let Some(chaddr) = self.chaddr_eth() {
510 write!(f, " chaddr={}", chaddr)?;
511 }
512
513 Ok(())
514 }
515}
516
517#[derive(Debug, Clone, PartialEq)]
519pub enum DhcpOption<'a> {
520 Pad,
521 End,
522 SubnetMask(Ipv4Addr),
523 Router(Vec<Ipv4Addr>),
524 DomainNameServer(Vec<Ipv4Addr>),
525 HostName(&'a [u8]),
526 DomainName(&'a [u8]),
527 RequestedIpAddress(Ipv4Addr),
528 IpAddressLeaseTime(u32),
529 MessageType(DhcpMessageType),
530 ServerIdentifier(Ipv4Addr),
531 ParameterRequestList(&'a [u8]),
532 RenewalTime(u32),
533 RebindingTime(u32),
534 ClientIdentifier(&'a [u8]),
535 Unknown { code: u8, data: &'a [u8] },
536}
537
538impl<'a> DhcpOption<'a> {
539 pub fn code(&self) -> Option<u8> {
541 match self {
542 DhcpOption::Pad => Some(0),
543 DhcpOption::End => Some(255),
544 DhcpOption::SubnetMask(_) => Some(1),
545 DhcpOption::Router(_) => Some(3),
546 DhcpOption::DomainNameServer(_) => Some(6),
547 DhcpOption::HostName(_) => Some(12),
548 DhcpOption::DomainName(_) => Some(15),
549 DhcpOption::RequestedIpAddress(_) => Some(50),
550 DhcpOption::IpAddressLeaseTime(_) => Some(51),
551 DhcpOption::MessageType(_) => Some(53),
552 DhcpOption::ServerIdentifier(_) => Some(54),
553 DhcpOption::ParameterRequestList(_) => Some(55),
554 DhcpOption::RenewalTime(_) => Some(58),
555 DhcpOption::RebindingTime(_) => Some(59),
556 DhcpOption::ClientIdentifier(_) => Some(61),
557 DhcpOption::Unknown { code, .. } => Some(*code),
558 }
559 }
560
561 fn parse(data: &'a [u8]) -> Option<(Self, &'a [u8])> {
563 if data.is_empty() {
564 return None;
565 }
566
567 let code = data[0];
568
569 if code == 0 {
571 return Some((DhcpOption::Pad, &data[1..]));
572 }
573 if code == 255 {
574 return Some((DhcpOption::End, &data[1..]));
575 }
576
577 if data.len() < 2 {
578 return None;
579 }
580
581 let length = data[1] as usize;
582 if data.len() < 2 + length {
583 return None;
584 }
585
586 let option_data = &data[2..2 + length];
587 let remaining = &data[2 + length..];
588
589 let option = match code {
590 1 if length == 4 => DhcpOption::SubnetMask(Ipv4Addr::from(u32::from_be_bytes(
591 option_data.try_into().ok()?,
592 ))),
593 3 if length.is_multiple_of(4) => {
594 let routers = option_data
595 .chunks_exact(4)
596 .map(|chunk| Ipv4Addr::from(u32::from_be_bytes(chunk.try_into().unwrap())))
597 .collect();
598 DhcpOption::Router(routers)
599 }
600 6 if length.is_multiple_of(4) => {
601 let servers = option_data
602 .chunks_exact(4)
603 .map(|chunk| Ipv4Addr::from(u32::from_be_bytes(chunk.try_into().unwrap())))
604 .collect();
605 DhcpOption::DomainNameServer(servers)
606 }
607 12 => DhcpOption::HostName(option_data),
608 15 => DhcpOption::DomainName(option_data),
609 50 if length == 4 => DhcpOption::RequestedIpAddress(Ipv4Addr::from(
610 u32::from_be_bytes(option_data.try_into().ok()?),
611 )),
612 51 if length == 4 => {
613 DhcpOption::IpAddressLeaseTime(u32::from_be_bytes(option_data.try_into().ok()?))
614 }
615 53 if length == 1 => DhcpOption::MessageType(DhcpMessageType::from_u8(option_data[0])?),
616 54 if length == 4 => DhcpOption::ServerIdentifier(Ipv4Addr::from(u32::from_be_bytes(
617 option_data.try_into().ok()?,
618 ))),
619 55 => DhcpOption::ParameterRequestList(option_data),
620 58 if length == 4 => {
621 DhcpOption::RenewalTime(u32::from_be_bytes(option_data.try_into().ok()?))
622 }
623 59 if length == 4 => {
624 DhcpOption::RebindingTime(u32::from_be_bytes(option_data.try_into().ok()?))
625 }
626 61 => DhcpOption::ClientIdentifier(option_data),
627 _ => DhcpOption::Unknown {
628 code,
629 data: option_data,
630 },
631 };
632
633 Some((option, remaining))
634 }
635}
636
637pub struct DhcpOptionsIter<'a> {
639 data: &'a [u8],
640 done: bool,
641}
642
643impl<'a> DhcpOptionsIter<'a> {
644 pub fn new(data: &'a [u8]) -> Self {
645 Self { data, done: false }
646 }
647}
648
649impl<'a> Iterator for DhcpOptionsIter<'a> {
650 type Item = DhcpOption<'a>;
651
652 fn next(&mut self) -> Option<Self::Item> {
653 if self.done || self.data.is_empty() {
654 return None;
655 }
656
657 let (option, remaining) = DhcpOption::parse(self.data)?;
658 self.data = remaining;
659
660 if matches!(option, DhcpOption::End) {
661 self.done = true;
662 }
663
664 Some(option)
665 }
666}
667
668#[cfg(test)]
669mod tests {
670 use super::*;
671 use std::str::FromStr;
672
673 fn create_basic_dhcp_discover() -> Vec<u8> {
674 let mut packet = vec![
675 1, 1, 6, 0, ];
680
681 packet.extend_from_slice(&0x12345678u32.to_be_bytes()); packet.extend_from_slice(&0u16.to_be_bytes()); packet.extend_from_slice(&0x8000u16.to_be_bytes()); packet.extend_from_slice(&[0; 4]); packet.extend_from_slice(&[0; 4]); packet.extend_from_slice(&[0; 4]); packet.extend_from_slice(&[0; 4]); packet.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
692 packet.extend_from_slice(&[0; 10]);
693
694 packet.extend_from_slice(&[0; 64]);
696
697 packet.extend_from_slice(&[0; 128]);
699
700 packet.extend_from_slice(&DHCP_MAGIC_COOKIE.to_be_bytes());
702
703 packet
704 }
705
706 #[test]
707 fn test_dhcp_discover_basic() {
708 let mut packet = create_basic_dhcp_discover();
709
710 packet.push(53); packet.push(1); packet.push(1); packet.push(255);
717
718 let (dhcp, remaining) = DhcpHeader::from_bytes(&packet).unwrap();
719
720 assert_eq!(remaining.len(), 0); assert_eq!(dhcp.op(), DhcpOpCode::BOOTREQUEST);
722 assert_eq!(dhcp.htype(), 1);
723 assert_eq!(dhcp.hlen(), 6);
724 assert_eq!(dhcp.xid(), 0x12345678);
725 assert!(dhcp.is_broadcast());
726 assert_eq!(dhcp.ciaddr(), Ipv4Addr::new(0, 0, 0, 0));
727
728 assert_eq!(
730 dhcp.chaddr_eth().unwrap(),
731 &EthAddr::from_str("aa:bb:cc:dd:ee:ff").unwrap()
732 );
733
734 let msg_type = dhcp.options().find_map(|opt| {
736 if let DhcpOption::MessageType(mt) = opt {
737 Some(mt)
738 } else {
739 None
740 }
741 });
742 assert_eq!(msg_type, Some(DhcpMessageType::DISCOVER));
743 }
744
745 #[test]
746 fn test_dhcp_offer() {
747 let mut packet = create_basic_dhcp_discover();
748 packet[0] = 2; packet[16..20].copy_from_slice(&[192, 168, 1, 100]);
752
753 packet.push(53);
755 packet.push(1);
756 packet.push(2); packet.push(54);
760 packet.push(4);
761 packet.extend_from_slice(&[192, 168, 1, 1]);
762
763 packet.push(1);
765 packet.push(4);
766 packet.extend_from_slice(&[255, 255, 255, 0]);
767
768 packet.push(3);
770 packet.push(4);
771 packet.extend_from_slice(&[192, 168, 1, 1]);
772
773 packet.push(6);
775 packet.push(4);
776 packet.extend_from_slice(&[8, 8, 8, 8]);
777
778 packet.push(51);
780 packet.push(4);
781 packet.extend_from_slice(&86400u32.to_be_bytes());
782
783 packet.push(255);
785
786 let (dhcp, _) = DhcpHeader::from_bytes(&packet).unwrap();
787
788 assert_eq!(dhcp.op(), DhcpOpCode::BOOTREPLY);
789 assert_eq!(dhcp.yiaddr(), Ipv4Addr::new(192, 168, 1, 100));
790
791 let options: Vec<_> = dhcp.options().collect();
793
794 assert!(options
796 .iter()
797 .any(|opt| matches!(opt, DhcpOption::MessageType(DhcpMessageType::OFFER))));
798
799 assert!(options.iter().any(|opt| matches!(opt, DhcpOption::ServerIdentifier(ip) if *ip == Ipv4Addr::new(192, 168, 1, 1))));
801
802 assert!(options.iter().any(|opt| matches!(opt, DhcpOption::SubnetMask(mask) if *mask == Ipv4Addr::new(255, 255, 255, 0))));
804
805 assert!(options.iter().any(|opt| matches!(opt, DhcpOption::Router(routers) if routers.len() == 1 && routers[0] == Ipv4Addr::new(192, 168, 1, 1))));
807
808 assert!(options.iter().any(|opt| matches!(opt, DhcpOption::DomainNameServer(dns) if dns.len() == 1 && dns[0] == Ipv4Addr::new(8, 8, 8, 8))));
810
811 assert!(options
813 .iter()
814 .any(|opt| matches!(opt, DhcpOption::IpAddressLeaseTime(86400))));
815 }
816
817 #[test]
818 fn test_dhcp_request() {
819 let mut packet = create_basic_dhcp_discover();
820
821 packet.push(53);
823 packet.push(1);
824 packet.push(3); packet.push(50);
828 packet.push(4);
829 packet.extend_from_slice(&[192, 168, 1, 100]);
830
831 packet.push(54);
833 packet.push(4);
834 packet.extend_from_slice(&[192, 168, 1, 1]);
835
836 packet.push(255);
838
839 let (dhcp, _) = DhcpHeader::from_bytes(&packet).unwrap();
840
841 let options: Vec<_> = dhcp.options().collect();
842
843 assert!(options
844 .iter()
845 .any(|opt| matches!(opt, DhcpOption::MessageType(DhcpMessageType::REQUEST))));
846 assert!(options.iter().any(|opt| matches!(opt, DhcpOption::RequestedIpAddress(ip) if *ip == Ipv4Addr::new(192, 168, 1, 100))));
847 assert!(options.iter().any(|opt| matches!(opt, DhcpOption::ServerIdentifier(ip) if *ip == Ipv4Addr::new(192, 168, 1, 1))));
848 }
849
850 #[test]
851 fn test_dhcp_ack() {
852 let mut packet = create_basic_dhcp_discover();
853 packet[0] = 2; packet[16..20].copy_from_slice(&[192, 168, 1, 100]);
857
858 packet.push(53);
860 packet.push(1);
861 packet.push(5); packet.push(255);
865
866 let (dhcp, _) = DhcpHeader::from_bytes(&packet).unwrap();
867
868 assert_eq!(dhcp.op(), DhcpOpCode::BOOTREPLY);
869 assert_eq!(dhcp.yiaddr(), Ipv4Addr::new(192, 168, 1, 100));
870
871 let msg_type = dhcp.options().find_map(|opt| {
873 if let DhcpOption::MessageType(mt) = opt {
874 Some(mt)
875 } else {
876 None
877 }
878 });
879 assert_eq!(msg_type, Some(DhcpMessageType::ACK));
880 }
881
882 #[test]
883 fn test_dhcp_nak() {
884 let mut packet = create_basic_dhcp_discover();
885 packet[0] = 2; packet.push(53);
889 packet.push(1);
890 packet.push(6); packet.push(255);
894
895 let (dhcp, _) = DhcpHeader::from_bytes(&packet).unwrap();
896
897 let msg_type = dhcp.options().find_map(|opt| {
898 if let DhcpOption::MessageType(mt) = opt {
899 Some(mt)
900 } else {
901 None
902 }
903 });
904 assert_eq!(msg_type, Some(DhcpMessageType::NAK));
905 }
906
907 #[test]
908 fn test_dhcp_invalid_magic_cookie() {
909 let mut packet = create_basic_dhcp_discover();
910
911 let magic_offset = 236;
913 packet[magic_offset..magic_offset + 4].copy_from_slice(&[0x11, 0x22, 0x33, 0x44]);
914
915 let result = DhcpHeader::from_bytes(&packet);
916 assert!(result.is_err());
917 }
918
919 #[test]
920 fn test_dhcp_packet_too_short() {
921 let packet = vec![0u8; 100]; let result = DhcpHeader::from_bytes(&packet);
923 assert!(result.is_err());
924 }
925
926 #[test]
927 fn test_dhcp_options_iterator() {
928 let mut packet = create_basic_dhcp_discover();
929
930 packet.push(53);
932 packet.push(1);
933 packet.push(1); packet.push(12); packet.push(4);
937 packet.extend_from_slice(b"test");
938
939 packet.push(61); packet.push(7);
941 packet.push(1); packet.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
943
944 packet.push(255); let (dhcp, _) = DhcpHeader::from_bytes(&packet).unwrap();
947
948 let options: Vec<_> = dhcp.options().collect();
949 assert!(options.len() >= 4, "Got {} options", options.len());
951
952 assert!(matches!(
954 options.iter().find(|opt| opt.code() == Some(53)),
955 Some(DhcpOption::MessageType(DhcpMessageType::DISCOVER))
956 ));
957
958 assert!(matches!(
960 options.iter().find(|opt| opt.code() == Some(12)),
961 Some(DhcpOption::HostName(b"test"))
962 ));
963 }
964
965 #[test]
966 fn test_dhcp_multiple_dns_servers() {
967 let mut packet = create_basic_dhcp_discover();
968
969 packet.push(6);
971 packet.push(8); packet.extend_from_slice(&[8, 8, 8, 8]); packet.extend_from_slice(&[8, 8, 4, 4]); packet.push(255);
976
977 let (dhcp, _) = DhcpHeader::from_bytes(&packet).unwrap();
978
979 let options: Vec<_> = dhcp.options().collect();
980
981 let dns_opt = options.iter().find_map(|opt| {
982 if let DhcpOption::DomainNameServer(dns) = opt {
983 Some(dns)
984 } else {
985 None
986 }
987 });
988
989 assert!(dns_opt.is_some());
990 let dns = dns_opt.unwrap();
991 assert_eq!(dns.len(), 2);
992 assert_eq!(dns[0], Ipv4Addr::new(8, 8, 8, 8));
993 assert_eq!(dns[1], Ipv4Addr::new(8, 8, 4, 4));
994 }
995
996 #[test]
997 fn test_dhcp_display() {
998 let mut packet = create_basic_dhcp_discover();
999 packet.push(53);
1000 packet.push(1);
1001 packet.push(1); packet.push(255);
1003
1004 let (dhcp, _) = DhcpHeader::from_bytes(&packet).unwrap();
1005 let display_str = format!("{}", dhcp);
1006
1007 assert!(
1008 display_str.contains("BOOTREQUEST"),
1009 "Display string: {}",
1010 display_str
1011 );
1012 assert!(
1013 display_str.contains("0x12345678"),
1014 "Display string: {}",
1015 display_str
1016 );
1017 let has_discover = dhcp
1019 .options()
1020 .any(|opt| matches!(opt, DhcpOption::MessageType(DhcpMessageType::DISCOVER)));
1021 assert!(has_discover);
1022 assert!(
1023 display_str.contains("aa:bb:cc:dd:ee:ff"),
1024 "Display string: {}",
1025 display_str
1026 );
1027 }
1028
1029 #[test]
1030 fn test_dhcp_broadcast_flag() {
1031 let mut packet = create_basic_dhcp_discover();
1032 packet.push(255);
1033
1034 let (dhcp, _) = DhcpHeader::from_bytes(&packet).unwrap();
1035 assert!(dhcp.is_broadcast());
1036
1037 packet[10..12].copy_from_slice(&0u16.to_be_bytes());
1039 let (dhcp2, _) = DhcpHeader::from_bytes(&packet).unwrap();
1040 assert!(!dhcp2.is_broadcast());
1041 }
1042
1043 #[test]
1044 fn test_dhcp_size_constants() {
1045 assert_eq!(std::mem::size_of::<DhcpHeader>(), 240);
1046 assert_eq!(DhcpHeader::FIXED_LEN, 240);
1047 }
1048
1049 #[test]
1050 fn test_dhcp_deref() {
1051 let mut packet = create_basic_dhcp_discover();
1052 packet.push(255);
1053
1054 let (dhcp, _) = DhcpHeader::from_bytes(&packet).unwrap();
1055
1056 assert_eq!(dhcp.op(), DhcpOpCode::BOOTREQUEST);
1058 assert_eq!(dhcp.xid(), 0x12345678);
1059 }
1060
1061 #[test]
1062 fn test_dhcp_unknown_option() {
1063 let mut packet = create_basic_dhcp_discover();
1064
1065 packet.push(200); packet.push(3);
1068 packet.extend_from_slice(&[1, 2, 3]);
1069
1070 packet.push(255);
1071
1072 let (dhcp, _) = DhcpHeader::from_bytes(&packet).unwrap();
1073
1074 let options: Vec<_> = dhcp.options().collect();
1075 assert!(options
1076 .iter()
1077 .any(|opt| matches!(opt, DhcpOption::Unknown { code: 200, .. })));
1078 }
1079
1080 #[test]
1081 fn test_dhcp_renewal_rebinding_times() {
1082 let mut packet = create_basic_dhcp_discover();
1083
1084 packet.push(58);
1086 packet.push(4);
1087 packet.extend_from_slice(&43200u32.to_be_bytes()); packet.push(59);
1091 packet.push(4);
1092 packet.extend_from_slice(&75600u32.to_be_bytes()); packet.push(255);
1095
1096 let (dhcp, _) = DhcpHeader::from_bytes(&packet).unwrap();
1097
1098 let options: Vec<_> = dhcp.options().collect();
1099
1100 assert!(options
1101 .iter()
1102 .any(|opt| matches!(opt, DhcpOption::RenewalTime(43200))));
1103 assert!(options
1104 .iter()
1105 .any(|opt| matches!(opt, DhcpOption::RebindingTime(75600))));
1106 }
1107
1108 #[test]
1109 fn test_dhcp_real_world_discover() {
1110 let mut packet = vec![
1112 1, 1, 6, 0, ];
1117
1118 packet.extend_from_slice(&0xABCDEF01u32.to_be_bytes());
1119 packet.extend_from_slice(&0u16.to_be_bytes());
1120 packet.extend_from_slice(&0x8000u16.to_be_bytes());
1121 packet.extend_from_slice(&[0; 4]); packet.extend_from_slice(&[0; 4]); packet.extend_from_slice(&[0; 4]); packet.extend_from_slice(&[0; 4]); packet.extend_from_slice(&[0x52, 0x54, 0x00, 0x12, 0x34, 0x56]);
1126 packet.extend_from_slice(&[0; 10]);
1127 packet.extend_from_slice(&[0; 64]);
1128 packet.extend_from_slice(&[0; 128]);
1129 packet.extend_from_slice(&DHCP_MAGIC_COOKIE.to_be_bytes());
1130
1131 packet.push(53);
1133 packet.push(1);
1134 packet.push(1); packet.push(55); packet.push(4);
1138 packet.extend_from_slice(&[1, 3, 6, 15]); packet.push(255);
1141
1142 let (dhcp, _) = DhcpHeader::from_bytes(&packet).unwrap();
1143
1144 assert_eq!(dhcp.op(), DhcpOpCode::BOOTREQUEST);
1145 assert_eq!(dhcp.xid(), 0xABCDEF01);
1146 assert!(dhcp.is_broadcast());
1147
1148 let msg_type = dhcp.options().find_map(|opt| {
1149 if let DhcpOption::MessageType(mt) = opt {
1150 Some(mt)
1151 } else {
1152 None
1153 }
1154 });
1155 assert_eq!(msg_type, Some(DhcpMessageType::DISCOVER));
1156 }
1157}