1use crate::pktparser;
21use std::collections;
22use std::fmt;
23use std::net;
24
25#[derive(Debug, PartialEq, Eq)]
26pub enum ParseError {
27 UnexpectedEndOfInput,
28 WrongMagic,
29 InvalidPacket,
30}
31
32impl std::error::Error for ParseError {
33 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
34 None
35 }
36}
37
38impl std::fmt::Display for ParseError {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 match self {
41 ParseError::UnexpectedEndOfInput => write!(f, "Unexpected End Of Input"),
42 ParseError::WrongMagic => write!(f, "Wrong Magic"),
43 ParseError::InvalidPacket => write!(f, "Invalid Packet"),
44 }
45 }
46}
47
48impl ParseError {
49 pub const fn get_variant_name(&self) -> &'static str {
50 use ParseError::*;
51 match self {
52 UnexpectedEndOfInput => "TRUNCATED_PACKET",
53 WrongMagic => "WRONG_MAGIC",
54 InvalidPacket => "INVALID_PACKET",
55 }
56 }
57}
58
59#[derive(PartialEq, Eq)]
60pub struct DhcpOp(u8);
61pub const OP_BOOTREQUEST: DhcpOp = DhcpOp(1);
62pub const OP_BOOTREPLY: DhcpOp = DhcpOp(2);
63
64impl std::fmt::Display for DhcpOp {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 match self {
67 &OP_BOOTREQUEST => write!(f, "BOOTREQUEST"),
68 &OP_BOOTREPLY => write!(f, "BOOTREPLY"),
69 DhcpOp(x) => write!(f, "#{}", x),
70 }
71 }
72}
73
74impl fmt::Debug for DhcpOp {
75 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
76 write!(f, "DhcpOp({})", self)
77 }
78}
79
80#[derive(PartialEq, Eq)]
81pub struct HwType(u8);
82pub const HWTYPE_ETHERNET: HwType = HwType(1);
83
84impl std::fmt::Display for HwType {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 match self {
87 &HWTYPE_ETHERNET => write!(f, "Ethernet"),
88 HwType(x) => write!(f, "#{x}"),
89 }
90 }
91}
92
93impl fmt::Debug for HwType {
94 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
95 write!(f, "HwType({})", self)
96 }
97}
98
99#[derive(Copy, Clone, PartialEq, Eq)]
100pub struct MessageType(u8);
101pub const DHCPDISCOVER: MessageType = MessageType(1);
102pub const DHCPOFFER: MessageType = MessageType(2);
103pub const DHCPREQUEST: MessageType = MessageType(3);
104pub const DHCPDECLINE: MessageType = MessageType(4);
105pub const DHCPACK: MessageType = MessageType(5);
106pub const DHCPNAK: MessageType = MessageType(6);
107pub const DHCPRELEASE: MessageType = MessageType(7);
108pub const DHCPINFORM: MessageType = MessageType(8);
109pub const DHCPFORCERENEW: MessageType = MessageType(9);
110
111impl std::fmt::Display for MessageType {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 match self {
114 &DHCPDISCOVER => write!(f, "DHCPDISCOVER"),
115 &DHCPOFFER => write!(f, "DHCPOFFER"),
116 &DHCPREQUEST => write!(f, "DHCPREQUEST"),
117 &DHCPDECLINE => write!(f, "DHCPDECLINE"),
118 &DHCPACK => write!(f, "DHCPACK"),
119 &DHCPNAK => write!(f, "DHCPNAK"),
120 &DHCPRELEASE => write!(f, "DHCPRELEASE"),
121 &DHCPINFORM => write!(f, "DHCPINFORM"),
122 &DHCPFORCERENEW => write!(f, "DHCPFORCERENEW"),
123 MessageType(x) => write!(f, "#{x}"),
124 }
125 }
126}
127
128impl fmt::Debug for MessageType {
129 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
130 write!(f, "{}", self)
131 }
132}
133
134impl std::default::Default for MessageType {
135 fn default() -> Self {
136 DHCPNAK
137 }
138}
139
140#[derive(Copy, Clone, PartialEq, Eq, Hash)]
141pub struct DhcpOption(u8);
142pub const OPTION_NETMASK: DhcpOption = DhcpOption(1);
143pub const OPTION_TIMEOFFSET: DhcpOption = DhcpOption(2);
144pub const OPTION_ROUTERADDR: DhcpOption = DhcpOption(3);
145pub const OPTION_TIMESERVER: DhcpOption = DhcpOption(4);
146pub const OPTION_NAMESERVER: DhcpOption = DhcpOption(5);
147pub const OPTION_DOMAINSERVER: DhcpOption = DhcpOption(6);
148pub const OPTION_LOGSERVER: DhcpOption = DhcpOption(7);
149pub const OPTION_QUOTESERVER: DhcpOption = DhcpOption(8);
150pub const OPTION_LPRSERVER: DhcpOption = DhcpOption(9);
151pub const OPTION_IMPRESSSERVER: DhcpOption = DhcpOption(10);
152pub const OPTION_RLPSERVER: DhcpOption = DhcpOption(11);
153pub const OPTION_HOSTNAME: DhcpOption = DhcpOption(12);
154pub const OPTION_DOMAINNAME: DhcpOption = DhcpOption(15);
155pub const OPTION_ROOTPATH: DhcpOption = DhcpOption(17);
156pub const OPTION_EXTFILE: DhcpOption = DhcpOption(18);
157pub const OPTION_FORWARD: DhcpOption = DhcpOption(19);
158pub const OPTION_SRCRT: DhcpOption = DhcpOption(20);
159pub const OPTION_MAXDGASSM: DhcpOption = DhcpOption(21);
160pub const OPTION_TTL: DhcpOption = DhcpOption(23);
161pub const OPTION_MTUTMOUT: DhcpOption = DhcpOption(24);
162pub const OPTION_MTUIF: DhcpOption = DhcpOption(26);
163pub const OPTION_MTUSUB: DhcpOption = DhcpOption(27);
164pub const OPTION_BROADCAST: DhcpOption = DhcpOption(28);
165pub const OPTION_MASKDISCOVERY: DhcpOption = DhcpOption(29);
166pub const OPTION_MASKSUPPLIER: DhcpOption = DhcpOption(30);
167pub const OPTION_RTRDISCOVERY: DhcpOption = DhcpOption(31);
168pub const OPTION_RTRREQ: DhcpOption = DhcpOption(32);
169pub const OPTION_STATICROUTE: DhcpOption = DhcpOption(33);
170pub const OPTION_TRAILERS: DhcpOption = DhcpOption(34);
171pub const OPTION_ARPTIMEOUT: DhcpOption = DhcpOption(35);
172pub const OPTION_ETHERNET: DhcpOption = DhcpOption(36);
173pub const OPTION_TCPTTL: DhcpOption = DhcpOption(37);
174pub const OPTION_TCPKEEPALIVE: DhcpOption = DhcpOption(38);
175pub const OPTION_TCPKEEPALIVEGARBAGE: DhcpOption = DhcpOption(39);
176pub const OPTION_NISDOMAIN: DhcpOption = DhcpOption(40);
177pub const OPTION_NISSERVERS: DhcpOption = DhcpOption(41);
178pub const OPTION_NTPSERVERS: DhcpOption = DhcpOption(42);
179pub const OPTION_VENDOR: DhcpOption = DhcpOption(43);
180pub const OPTION_NETBIOSNAMESRV: DhcpOption = DhcpOption(44);
181pub const OPTION_NETBIOSDISTSRV: DhcpOption = DhcpOption(45);
182pub const OPTION_NETBIOSTYPE: DhcpOption = DhcpOption(46);
183pub const OPTION_NETBIOSSCOPE: DhcpOption = DhcpOption(47);
184pub const OPTION_XWFONTSRVS: DhcpOption = DhcpOption(48);
185pub const OPTION_XWDISPLAY: DhcpOption = DhcpOption(49);
186pub const OPTION_ADDRESSREQUEST: DhcpOption = DhcpOption(50);
187pub const OPTION_LEASETIME: DhcpOption = DhcpOption(51);
188pub const OPTION_MSGTYPE: DhcpOption = DhcpOption(53);
189pub const OPTION_SERVERID: DhcpOption = DhcpOption(54);
190pub const OPTION_PARAMLIST: DhcpOption = DhcpOption(55);
191pub const OPTION_MESSAGE: DhcpOption = DhcpOption(56);
192pub const OPTION_MAXMSGSIZE: DhcpOption = DhcpOption(57);
193pub const OPTION_RENEWALTIME: DhcpOption = DhcpOption(58);
194pub const OPTION_REBINDTIME: DhcpOption = DhcpOption(59);
195pub const OPTION_VENDOR_CLASS: DhcpOption = DhcpOption(60);
196pub const OPTION_CLIENTID: DhcpOption = DhcpOption(61);
197pub const OPTION_NIS3DOMAIN: DhcpOption = DhcpOption(64);
198pub const OPTION_NIS3SERVERS: DhcpOption = DhcpOption(65);
199pub const OPTION_HOMEAGENT: DhcpOption = DhcpOption(68);
200pub const OPTION_SMTP: DhcpOption = DhcpOption(69);
201pub const OPTION_POP3: DhcpOption = DhcpOption(70);
202pub const OPTION_NNTP: DhcpOption = DhcpOption(71);
203pub const OPTION_WWW: DhcpOption = DhcpOption(72);
204pub const OPTION_FINGER: DhcpOption = DhcpOption(73);
205pub const OPTION_IRC: DhcpOption = DhcpOption(74);
206pub const OPTION_STREETTALK: DhcpOption = DhcpOption(75);
207pub const OPTION_STDA: DhcpOption = DhcpOption(76);
208pub const OPTION_USERCLASS: DhcpOption = DhcpOption(77); pub const OPTION_FQDN: DhcpOption = DhcpOption(81); pub const OPTION_UUID: DhcpOption = DhcpOption(97); pub const OPTION_PCODE: DhcpOption = DhcpOption(100); pub const OPTION_TCODE: DhcpOption = DhcpOption(101); pub const OPTION_AUTOCONF: DhcpOption = DhcpOption(103);
214pub const OPTION_SUBNETSELECT: DhcpOption = DhcpOption(104);
215pub const OPTION_IPV6PREFERRED: DhcpOption = DhcpOption(108);
216pub const OPTION_CAPTIVEPORTAL: DhcpOption = DhcpOption(114); pub const OPTION_DOMAINSEARCH: DhcpOption = DhcpOption(119);
218pub const OPTION_SIPSERVERS: DhcpOption = DhcpOption(120);
219pub const OPTION_CIDRROUTE: DhcpOption = DhcpOption(121);
220pub const OPTION_WPAD: DhcpOption = DhcpOption(252);
221
222const OPT_INFO: &[(&str, DhcpOption, DhcpOptionType)] = &[
223 ("netmask", OPTION_NETMASK, DhcpOptionType::Ip),
224 ("time-offset", OPTION_TIMEOFFSET, DhcpOptionType::I32),
225 ("routers", OPTION_ROUTERADDR, DhcpOptionType::IpList),
226 ("time-servers", OPTION_TIMESERVER, DhcpOptionType::IpList),
227 ("name-servers", OPTION_NAMESERVER, DhcpOptionType::IpList),
228 ("dns-servers", OPTION_DOMAINSERVER, DhcpOptionType::IpList),
229 ("log-servers", OPTION_LOGSERVER, DhcpOptionType::IpList),
230 ("quote-servers", OPTION_QUOTESERVER, DhcpOptionType::IpList),
231 ("lpr-servers", OPTION_LPRSERVER, DhcpOptionType::IpList),
232 (
234 "impress-servers",
235 OPTION_IMPRESSSERVER,
236 DhcpOptionType::IpList,
237 ),
238 ("rlp-servers", OPTION_RLPSERVER, DhcpOptionType::IpList),
239 ("host-name", OPTION_HOSTNAME, DhcpOptionType::String),
240 ("domain-name", OPTION_DOMAINNAME, DhcpOptionType::String),
243 ("root-path", OPTION_ROOTPATH, DhcpOptionType::String),
245 ("extension-file", OPTION_EXTFILE, DhcpOptionType::String),
246 ("forward", OPTION_FORWARD, DhcpOptionType::Bool),
247 ("source-route", OPTION_SRCRT, DhcpOptionType::Bool),
249 (
251 "max-reassembly",
252 OPTION_MAXDGASSM,
253 DhcpOptionType::Seconds16,
254 ),
255 ("default-ttl", OPTION_TTL, DhcpOptionType::U8),
256 ("mtu-timeout", OPTION_MTUTMOUT, DhcpOptionType::Seconds32),
257 ("mtu", OPTION_MTUIF, DhcpOptionType::U16),
259 ("mtu-subnet", OPTION_MTUSUB, DhcpOptionType::Bool),
260 ("broadcast", OPTION_BROADCAST, DhcpOptionType::Ip),
261 ("mask-discovery", OPTION_MASKDISCOVERY, DhcpOptionType::Bool),
262 ("mask-supplier", OPTION_MASKSUPPLIER, DhcpOptionType::Bool),
264 (
265 "router-discovery",
266 OPTION_RTRDISCOVERY,
267 DhcpOptionType::Bool,
268 ),
269 ("router-request", OPTION_RTRREQ, DhcpOptionType::Ip),
270 (
271 "classful-route",
272 OPTION_STATICROUTE,
273 DhcpOptionType::Unknown,
274 ), ("trailers", OPTION_TRAILERS, DhcpOptionType::Bool),
276 ("arp-timeout", OPTION_ARPTIMEOUT, DhcpOptionType::Seconds32),
277 ("ethernet", OPTION_ETHERNET, DhcpOptionType::Bool),
278 ("tcp-ttl", OPTION_TCPTTL, DhcpOptionType::U16),
279 (
280 "tcp-keepalive",
281 OPTION_TCPKEEPALIVE,
282 DhcpOptionType::Seconds32,
283 ),
284 (
285 "tcp-keepalive-garbage",
286 OPTION_TCPKEEPALIVEGARBAGE,
287 DhcpOptionType::Bool,
288 ),
289 ("nis-domain", OPTION_NISDOMAIN, DhcpOptionType::String),
291 ("nis-servers", OPTION_NISSERVERS, DhcpOptionType::IpList),
292 ("ntp-servers", OPTION_NTPSERVERS, DhcpOptionType::IpList),
293 ("vendor", OPTION_VENDOR, DhcpOptionType::Unknown),
295 (
296 "netbios-namesrv",
297 OPTION_NETBIOSNAMESRV,
298 DhcpOptionType::IpList,
299 ),
300 (
301 "netbios-distsrv",
302 OPTION_NETBIOSDISTSRV,
303 DhcpOptionType::IpList,
304 ),
305 ("netbios-type", OPTION_NETBIOSTYPE, DhcpOptionType::U8), ("netbios-scope", OPTION_NETBIOSSCOPE, DhcpOptionType::String),
307 (
308 "xwindow-font-servers",
309 OPTION_XWFONTSRVS,
310 DhcpOptionType::IpList,
311 ),
312 ("xwindow-display", OPTION_XWDISPLAY, DhcpOptionType::IpList),
313 ("address-request", OPTION_ADDRESSREQUEST, DhcpOptionType::Ip),
315 ("lease-time", OPTION_LEASETIME, DhcpOptionType::Seconds32),
316 ("server-id", OPTION_SERVERID, DhcpOptionType::Ip),
319 ("message", OPTION_MESSAGE, DhcpOptionType::String),
321 ("max-size", OPTION_MAXMSGSIZE, DhcpOptionType::U16),
322 (
323 "renewal-time",
324 OPTION_RENEWALTIME,
325 DhcpOptionType::Seconds16,
326 ), ("rebind-time", OPTION_REBINDTIME, DhcpOptionType::Seconds16), ("class-id", OPTION_VENDOR_CLASS, DhcpOptionType::String),
330 ("client-id", OPTION_CLIENTID, DhcpOptionType::HwAddr),
331 ("nisplus-domain", OPTION_NIS3DOMAIN, DhcpOptionType::String),
334 (
335 "nisplus-servers",
336 OPTION_NIS3SERVERS,
337 DhcpOptionType::IpList,
338 ),
339 (
342 "home-agent-servers",
343 OPTION_HOMEAGENT,
344 DhcpOptionType::IpList,
345 ),
346 ("smtp-servers", OPTION_SMTP, DhcpOptionType::IpList),
347 ("pop3-servers", OPTION_POP3, DhcpOptionType::IpList),
349 ("nntp-servers", OPTION_NNTP, DhcpOptionType::IpList),
350 ("www-servers", OPTION_WWW, DhcpOptionType::IpList),
351 ("finger-servers", OPTION_FINGER, DhcpOptionType::IpList),
352 ("irc-servers", OPTION_IRC, DhcpOptionType::IpList),
353 (
354 "streettalk-servers",
355 OPTION_STREETTALK,
356 DhcpOptionType::IpList,
357 ),
358 ("stda-servers", OPTION_STDA, DhcpOptionType::IpList),
359 ("user-class", OPTION_USERCLASS, DhcpOptionType::String),
360 ("fqdn", OPTION_FQDN, DhcpOptionType::String),
365 ("uuid", OPTION_UUID, DhcpOptionType::Unknown), ("tz-rule", OPTION_PCODE, DhcpOptionType::String),
385 ("tz-name", OPTION_TCODE, DhcpOptionType::String),
386 ("autoconfig", OPTION_AUTOCONF, DhcpOptionType::Bool),
388 ("subnet-selection", OPTION_SUBNETSELECT, DhcpOptionType::Ip), (
390 "dns-searches",
391 OPTION_DOMAINSEARCH,
392 DhcpOptionType::DomainList,
393 ), (
395 "ipv6-preferred",
396 OPTION_IPV6PREFERRED,
397 DhcpOptionType::Seconds32,
398 ), (
400 "captive-portal",
401 OPTION_CAPTIVEPORTAL,
402 DhcpOptionType::String,
403 ), ("sip-servers", OPTION_SIPSERVERS, DhcpOptionType::Unknown), ("routes", OPTION_CIDRROUTE, DhcpOptionType::Routes),
406 ("wpad-url", OPTION_WPAD, DhcpOptionType::String),
411];
412
413impl From<u8> for DhcpOption {
414 fn from(v: u8) -> Self {
415 DhcpOption(v)
416 }
417}
418
419#[derive(Copy, Clone)]
420pub enum DhcpOptionType {
421 String,
422 Ip,
423 IpList,
424 I32,
425 U8,
426 U16,
427 U32,
428 Bool,
429 Seconds16,
430 Seconds32,
431 HwAddr,
432 Routes,
433 DomainList,
434 Unknown,
435}
436
437type IpList = Vec<std::net::Ipv4Addr>;
438type U8Str = Vec<u8>;
439
440impl DhcpOptionType {
441 pub fn decode(&self, v: &[u8]) -> Option<DhcpOptionTypeValue> {
442 match *self {
443 DhcpOptionType::String => U8Str::parse_into(v)
444 .map(|x| DhcpOptionTypeValue::String(String::from_utf8_lossy(&x).to_string())),
445 DhcpOptionType::Ip => std::net::Ipv4Addr::parse_into(v).map(DhcpOptionTypeValue::Ip),
446 DhcpOptionType::IpList => IpList::parse_into(v).map(DhcpOptionTypeValue::IpList),
447 DhcpOptionType::I32 => i32::parse_into(v).map(DhcpOptionTypeValue::I32),
448 DhcpOptionType::U8 => u8::parse_into(v).map(DhcpOptionTypeValue::U8),
449 DhcpOptionType::U16 => u16::parse_into(v).map(DhcpOptionTypeValue::U16),
450 DhcpOptionType::U32 => u32::parse_into(v).map(DhcpOptionTypeValue::U32),
451 DhcpOptionType::Bool => u8::parse_into(v).map(DhcpOptionTypeValue::U8), DhcpOptionType::Seconds16 => u16::parse_into(v).map(DhcpOptionTypeValue::U16), DhcpOptionType::Seconds32 => u32::parse_into(v).map(DhcpOptionTypeValue::U32), DhcpOptionType::HwAddr => U8Str::parse_into(v).map(DhcpOptionTypeValue::HwAddr),
455 DhcpOptionType::Routes => Vec::<Route>::parse_into(v).map(DhcpOptionTypeValue::Routes),
456 DhcpOptionType::DomainList => {
457 Vec::<String>::parse_into(v).map(DhcpOptionTypeValue::DomainList)
458 }
459 DhcpOptionType::Unknown => U8Str::parse_into(v).map(DhcpOptionTypeValue::Unknown),
460 }
461 }
462}
463
464#[derive(Debug, Clone)]
465pub enum DhcpOptionTypeValue {
466 String(String),
467 IpList(IpList),
468 Ip(std::net::Ipv4Addr),
469 I32(i32),
470 U8(u8),
471 U16(u16),
472 U32(u32),
473 HwAddr(Vec<u8>),
474 Routes(Vec<Route>),
475 DomainList(Vec<String>),
476 Unknown(Vec<u8>),
477}
478
479impl DhcpOptionTypeValue {
480 pub fn as_bytes(&self) -> Vec<u8> {
481 match self {
482 DhcpOptionTypeValue::String(s) => s.as_bytes().to_vec(),
483 DhcpOptionTypeValue::IpList(v) => {
484 v.iter().map(|x| x.octets()).fold(vec![], |mut acc, v| {
485 acc.extend(v.iter());
486 acc
487 })
488 }
489 DhcpOptionTypeValue::Ip(i) => i.octets().to_vec(),
490 DhcpOptionTypeValue::I32(x) => x.to_be_bytes().to_vec(),
491 DhcpOptionTypeValue::U8(x) => x.to_be_bytes().to_vec(),
492 DhcpOptionTypeValue::U16(x) => x.to_be_bytes().to_vec(),
493 DhcpOptionTypeValue::U32(x) => x.to_be_bytes().to_vec(),
494 DhcpOptionTypeValue::HwAddr(x) => x.clone(),
495 DhcpOptionTypeValue::Routes(v) => {
496 let mut o = vec![];
497 for i in v {
498 o.push(i.prefix.prefixlen);
499 o.extend(i.prefix.addr.octets().iter());
500 o.extend(i.nexthop.octets().iter());
501 }
502 o
503 }
504 DhcpOptionTypeValue::Unknown(v) => v.clone(),
505 DhcpOptionTypeValue::DomainList(l) => {
506 let mut o = vec![];
507 for domains in l.iter().map(|d| d.split('.')) {
508 for label in domains {
509 o.push(label.len() as u8);
510 o.extend(label.as_bytes());
511 }
512 o.push(0_u8)
513 }
514 o
515 }
516 }
517 }
518}
519
520fn escape_char(&c: &u8) -> String {
521 match c {
522 b' '..=b'~' => char::from(c).to_string(),
523 x => format!("\\x{:0>2x}", x),
524 }
525}
526
527fn escape_str(c: &[u8]) -> String {
528 c.iter().map(escape_char).collect::<Vec<String>>().join("")
529}
530
531impl std::fmt::Display for DhcpOptionTypeValue {
532 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
533 match self {
534 DhcpOptionTypeValue::String(s) => write!(f, "{}", escape_str(s.as_bytes())),
535 DhcpOptionTypeValue::Ip(i) => i.fmt(f),
536 DhcpOptionTypeValue::U8(i) => i.fmt(f),
537 DhcpOptionTypeValue::U16(i) => i.fmt(f),
538 DhcpOptionTypeValue::U32(i) => i.fmt(f),
539 DhcpOptionTypeValue::I32(i) => i.fmt(f),
540 DhcpOptionTypeValue::IpList(l) => write!(
541 f,
542 "{}",
543 l.iter()
544 .map(|i| format!("{}", i))
545 .collect::<Vec<String>>()
546 .join(",")
547 ),
548 DhcpOptionTypeValue::HwAddr(x) => write!(
549 f,
550 "{}",
551 x.iter()
552 .map(|b| format!("{:0>2x}", b))
553 .collect::<Vec<String>>()
554 .join(":")
555 ),
556 DhcpOptionTypeValue::Routes(l) => write!(
557 f,
558 "{}",
559 l.iter()
560 .map(|i| format!("{}->{}", i.prefix, i.nexthop))
561 .collect::<Vec<String>>()
562 .join(",")
563 ),
564 DhcpOptionTypeValue::Unknown(v) => write!(
565 f,
566 "{}",
567 v.iter()
568 .map(|b| format!("{:0>2x}", b))
569 .collect::<Vec<_>>()
570 .join("")
571 ),
572 DhcpOptionTypeValue::DomainList(v) => write!(f, "{}", v.join(",")),
573 }
574 }
575}
576
577impl DhcpOption {
578 pub const fn new(opt: u8) -> Self {
579 DhcpOption(opt)
580 }
581 pub fn get_type(&self) -> Option<DhcpOptionType> {
582 for (_name, option, ty) in OPT_INFO {
583 if option == self {
584 return Some(*ty);
585 }
586 }
587 None
588 }
589}
590
591pub fn name_to_option(lookup_name: &str) -> Option<DhcpOption> {
592 for (name, option, _ty) in OPT_INFO {
593 if *name == lookup_name {
594 return Some(*option);
595 }
596 }
597 None
598}
599
600impl std::fmt::Display for DhcpOption {
601 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
602 for (name, option, _ty) in OPT_INFO {
603 if option == self {
604 return write!(f, "{name}");
605 }
606 }
607 write!(f, "#{}", self.0)
608 }
609}
610
611impl fmt::Debug for DhcpOption {
612 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
613 write!(f, "{}", self)
614 }
615}
616
617pub trait DhcpParse {
618 type Item;
619 fn parse_into(v: &[u8]) -> Option<Self::Item>;
620}
621
622#[derive(Clone, Debug)]
623pub struct Route {
624 pub prefix: erbium_net::Ipv4Subnet,
625 pub nexthop: std::net::Ipv4Addr,
626}
627
628fn parse_ip_from_iter<I>(it: &mut I) -> Option<std::net::Ipv4Addr>
629where
630 I: std::iter::Iterator<Item = u8>,
631{
632 let ip1 = it.next()?;
633 let ip2 = it.next()?;
634 let ip3 = it.next()?;
635 let ip4 = it.next()?;
636 Some(net::Ipv4Addr::new(ip1, ip2, ip3, ip4))
637}
638
639impl DhcpParse for Vec<Route> {
640 type Item = Self;
641 fn parse_into(v: &[u8]) -> Option<Self::Item> {
642 let mut it = v.iter().copied();
643 let mut ret = vec![];
644 while let Some(prefixlen) = it.next() {
645 let prefix =
646 erbium_net::Ipv4Subnet::new(parse_ip_from_iter(&mut it)?, prefixlen).ok()?;
647 let nexthop = parse_ip_from_iter(&mut it)?;
648 ret.push(Route { prefix, nexthop });
649 }
650 Some(ret)
651 }
652}
653
654impl DhcpParse for std::net::Ipv4Addr {
655 type Item = Self;
656 fn parse_into(v: &[u8]) -> Option<Self::Item> {
657 if v.len() != 4 {
658 None
659 } else {
660 Some(std::net::Ipv4Addr::new(v[0], v[1], v[2], v[3]))
661 }
662 }
663}
664
665impl DhcpParse for IpList {
666 type Item = Self;
667 fn parse_into(v: &[u8]) -> Option<Self::Item> {
668 let mut it = v.iter().copied();
669 let mut ret = vec![];
670 while let Some(o1) = it.next() {
671 let o2 = it.next();
672 let o3 = it.next();
673 let o4 = it.next();
674 ret.push(std::net::Ipv4Addr::new(o1, o2?, o3?, o4?));
675 }
676 Some(ret)
677 }
678}
679
680impl DhcpParse for Vec<u8> {
684 type Item = Self;
685 fn parse_into(v: &[u8]) -> Option<Self> {
686 Some(v.to_vec())
687 }
688}
689
690impl DhcpParse for u64 {
693 type Item = Self;
694 fn parse_into(v: &[u8]) -> Option<Self> {
695 Some(v.iter().fold(0_u64, |acc, &v| (acc << 8) + (v as Self)))
696 }
697}
698
699impl DhcpParse for u32 {
700 type Item = Self;
701 fn parse_into(v: &[u8]) -> Option<Self> {
702 Some(v.iter().fold(0_u32, |acc, &v| (acc << 8) + (v as Self)))
703 }
704}
705
706impl DhcpParse for u16 {
707 type Item = Self;
708 fn parse_into(v: &[u8]) -> Option<Self> {
709 Some(v.iter().fold(0_u16, |acc, &v| (acc << 8) + (v as Self)))
710 }
711}
712
713impl DhcpParse for i32 {
714 type Item = Self;
715 fn parse_into(v: &[u8]) -> Option<Self> {
716 Some(v.iter().fold(0_i32, |acc, &v| (acc << 8) + (v as Self)))
717 }
718}
719
720impl DhcpParse for u8 {
721 type Item = Self;
722 fn parse_into(v: &[u8]) -> Option<Self> {
723 if v.len() != 1 {
724 None
725 } else {
726 v.first().copied()
727 }
728 }
729}
730
731impl DhcpParse for std::time::Duration {
732 type Item = Self;
733 fn parse_into(v: &[u8]) -> Option<Self> {
734 u64::parse_into(v).map(std::time::Duration::from_secs)
735 }
736}
737
738impl DhcpParse for MessageType {
739 type Item = Self;
740 fn parse_into(v: &[u8]) -> Option<Self> {
741 if v.len() != 1 {
742 None
743 } else {
744 Some(MessageType(v[0]))
745 }
746 }
747}
748
749impl DhcpParse for Vec<String> {
750 type Item = Self;
751 fn parse_into(v: &[u8]) -> Option<Self> {
752 let mut buf = crate::pktparser::Buffer::new(v);
753 Some(buf.get_domains()?.iter().map(|d| d.join(".")).collect())
754 }
755}
756
757impl DhcpParse for String {
758 type Item = Self;
759 fn parse_into(v: &[u8]) -> Option<Self> {
760 Some(String::from_utf8_lossy(v).to_string())
761 }
762}
763
764#[derive(Debug, Clone, PartialEq, Default, Eq)]
765pub struct DhcpOptions {
766 pub other: collections::HashMap<DhcpOption, Vec<u8>>,
767}
768
769impl DhcpOptions {
770 pub fn get_raw_option(&self, option: &DhcpOption) -> Option<&[u8]> {
771 self.other.get(option).map(|x| x.as_slice())
772 }
773
774 pub fn get_option<T: DhcpParse>(&self, option: &DhcpOption) -> Option<T::Item> {
775 self.get_raw_option(option).and_then(|x| T::parse_into(x))
776 }
777
778 pub fn get_serverid(&self) -> Option<std::net::Ipv4Addr> {
779 self.get_option::<std::net::Ipv4Addr>(&OPTION_SERVERID)
780 }
781
782 pub fn get_clientid(&self) -> Option<Vec<u8>> {
783 self.get_option::<Vec<u8>>(&OPTION_CLIENTID)
784 }
785
786 pub fn get_address_request(&self) -> Option<net::Ipv4Addr> {
787 self.get_option::<std::net::Ipv4Addr>(&OPTION_ADDRESSREQUEST)
788 }
789
790 pub fn get_messagetype(&self) -> Option<MessageType> {
791 self.get_option::<MessageType>(&OPTION_MSGTYPE)
792 }
793
794 pub fn get_hostname(&self) -> Option<String> {
795 self.get_option::<String>(&OPTION_HOSTNAME)
796 }
797
798 #[must_use]
799 pub fn set_raw_option(mut self, option: &DhcpOption, value: &[u8]) -> Self {
800 self.other.insert(*option, value.to_vec());
801 self
802 }
803
804 #[must_use]
805 pub fn set_option<T: Serialise>(self, option: &DhcpOption, value: &T) -> Self {
806 let mut v = Vec::new();
807 value.serialise(&mut v);
808 self.set_raw_option(option, &v)
809 }
810
811 pub fn mutate_option<T: Serialise>(&mut self, option: &DhcpOption, value: &T) {
812 let mut v = Vec::new();
813 value.serialise(&mut v);
814 self.other.insert(*option, v);
815 }
816
817 pub fn mutate_option_value(&mut self, option: &DhcpOption, value: &DhcpOptionTypeValue) {
818 self.other.insert(*option, value.as_bytes());
819 }
820
821 #[must_use]
822 pub fn maybe_set_option<T: Serialise>(self, option: &DhcpOption, value: Option<&T>) -> Self {
823 if let Some(v) = value {
824 self.set_option(option, v)
825 } else {
826 self
827 }
828 }
829
830 #[must_use]
831 pub fn remove_option(mut self, option: &DhcpOption) -> Self {
832 self.other.remove(option);
833 self
834 }
835}
836
837#[derive(PartialEq, Eq)]
838pub struct Dhcp {
839 pub op: DhcpOp,
840 pub htype: HwType,
841 pub hlen: u8,
842 pub hops: u8,
843 pub xid: u32,
844 pub secs: u16,
845 pub flags: u16,
846 pub ciaddr: net::Ipv4Addr,
847 pub yiaddr: net::Ipv4Addr,
848 pub siaddr: net::Ipv4Addr,
849 pub giaddr: net::Ipv4Addr,
850 pub chaddr: Vec<u8>,
851 pub sname: Vec<u8>,
852 pub file: Vec<u8>,
853 pub options: DhcpOptions,
854}
855
856impl std::fmt::Debug for Dhcp {
857 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
858 f.debug_struct("Dhcp")
859 .field("op", &self.op)
860 .field("htype", &self.htype)
861 .field("hlen", &self.hlen)
862 .field("hops", &self.hops)
863 .field("xid", &self.xid)
864 .field("secs", &self.secs)
865 .field("flags", &self.flags)
866 .field("ciaddr", &self.ciaddr)
867 .field("yiaddr", &self.yiaddr)
868 .field("siaddr", &self.siaddr)
869 .field("giaddr", &self.giaddr)
870 .field(
871 "chaddr",
872 &self
873 .chaddr
874 .iter()
875 .map(|&x| format!("{:x?}", x))
876 .collect::<Vec<String>>()
877 .join(""),
878 )
879 .field(
880 "sname",
881 &self
882 .sname
883 .iter()
884 .map(|&x| format!("{:x?}", x))
885 .collect::<Vec<String>>()
886 .join(""),
887 )
888 .field(
889 "file",
890 &String::from_utf8(self.file.clone())
891 .or_else::<Result<String, String>, _>(|_| Ok(format!("{:?}", self.file)))
892 .unwrap(),
893 )
894 .field("options", &self.options)
895 .finish()
896 }
897}
898
899fn null_terminated(mut v: Vec<u8>) -> Vec<u8> {
900 for i in 0..v.len() {
901 if v[i] == 0 {
902 v.truncate(i);
903 break;
904 }
905 }
906 v
907}
908
909pub fn parse_options(mut buf: pktparser::Buffer) -> Result<DhcpOptions, ParseError> {
910 let mut raw_options: collections::HashMap<DhcpOption, Vec<u8>> = collections::HashMap::new();
911 loop {
912 match buf.get_u8() {
913 Some(0) => (), Some(255) => break, Some(x) => {
916 let l = buf.get_u8().ok_or(ParseError::UnexpectedEndOfInput)?;
917 raw_options.entry(DhcpOption(x)).or_default().extend(
918 buf.get_bytes(l as usize)
919 .ok_or(ParseError::UnexpectedEndOfInput)?,
920 );
921 }
922 None => return Err(ParseError::UnexpectedEndOfInput),
923 }
924 }
925
926 Ok(DhcpOptions { other: raw_options })
927}
928
929pub fn parse(pkt: &[u8]) -> Result<Dhcp, ParseError> {
930 let mut buf = pktparser::Buffer::new(pkt);
931 let op = buf.get_u8().ok_or(ParseError::UnexpectedEndOfInput)?;
932 let htype = buf.get_u8().ok_or(ParseError::UnexpectedEndOfInput)?;
933 let hlen = buf.get_u8().ok_or(ParseError::UnexpectedEndOfInput)?;
934 let hops = buf.get_u8().ok_or(ParseError::UnexpectedEndOfInput)?;
935 let xid = buf.get_be32().ok_or(ParseError::UnexpectedEndOfInput)?;
936 let secs = buf.get_be16().ok_or(ParseError::UnexpectedEndOfInput)?;
937 let flags = buf.get_be16().ok_or(ParseError::UnexpectedEndOfInput)?;
938 let ciaddr = buf.get_ipv4().ok_or(ParseError::UnexpectedEndOfInput)?;
939 let yiaddr = buf.get_ipv4().ok_or(ParseError::UnexpectedEndOfInput)?;
940 let siaddr = buf.get_ipv4().ok_or(ParseError::UnexpectedEndOfInput)?;
941 let giaddr = buf.get_ipv4().ok_or(ParseError::UnexpectedEndOfInput)?;
942 let chaddr = buf.get_vec(16).ok_or(ParseError::UnexpectedEndOfInput)?;
943 if hlen as usize > chaddr.len() {
944 return Err(ParseError::InvalidPacket);
945 }
946 let sname = null_terminated(buf.get_vec(64).ok_or(ParseError::UnexpectedEndOfInput)?);
947 let file = null_terminated(buf.get_vec(128).ok_or(ParseError::UnexpectedEndOfInput)?);
948 let magic = buf.get_be32().ok_or(ParseError::UnexpectedEndOfInput)?;
949 if magic != 0x6382_5363 {
950 return Err(ParseError::WrongMagic);
951 }
952 let options = parse_options(buf)?;
953
954 Ok(Dhcp {
955 op: DhcpOp(op),
956 htype: HwType(htype),
957 hlen,
958 hops,
959 xid,
960 secs,
961 flags,
962 ciaddr,
963 yiaddr,
964 siaddr,
965 giaddr,
966 chaddr: chaddr[0..hlen as usize].to_vec(),
967 sname,
968 file,
969 options,
970 })
971}
972
973pub trait Serialise {
974 fn serialise(&self, v: &mut Vec<u8>);
975}
976
977impl Serialise for u8 {
978 fn serialise(&self, v: &mut Vec<u8>) {
979 v.push(*self);
980 }
981}
982
983impl Serialise for u16 {
984 fn serialise(&self, v: &mut Vec<u8>) {
985 for b in self.to_be_bytes().iter() {
986 b.serialise(v);
987 }
988 }
989}
990
991impl Serialise for u32 {
992 fn serialise(&self, v: &mut Vec<u8>) {
993 for b in self.to_be_bytes().iter() {
994 b.serialise(v);
995 }
996 }
997}
998
999impl Serialise for net::Ipv4Addr {
1000 fn serialise(&self, v: &mut Vec<u8>) {
1001 for b in self.octets().iter() {
1002 b.serialise(v);
1003 }
1004 }
1005}
1006
1007impl Serialise for DhcpOption {
1008 fn serialise(&self, v: &mut Vec<u8>) {
1009 self.0.serialise(v);
1010 }
1011}
1012
1013impl Serialise for &[u8] {
1014 fn serialise(&self, v: &mut Vec<u8>) {
1015 v.extend(*self);
1016 }
1017}
1018
1019impl Serialise for MessageType {
1020 fn serialise(&self, v: &mut Vec<u8>) {
1021 self.0.serialise(v);
1022 }
1023}
1024
1025impl<T: Serialise> Serialise for Vec<T> {
1026 fn serialise(&self, v: &mut Vec<u8>) {
1027 for i in self {
1028 i.serialise(v);
1029 }
1030 }
1031}
1032
1033impl Serialise for String {
1034 fn serialise(&self, v: &mut Vec<u8>) {
1035 self.as_bytes().serialise(v)
1036 }
1037}
1038
1039impl Serialise for i32 {
1040 fn serialise(&self, v: &mut Vec<u8>) {
1041 for i in self.to_be_bytes().iter() {
1042 i.serialise(v)
1043 }
1044 }
1045}
1046
1047impl Serialise for DhcpOptionTypeValue {
1048 fn serialise(&self, v: &mut Vec<u8>) {
1049 v.extend(self.as_bytes().iter());
1050 }
1051}
1052
1053fn serialise_option<T>(option: DhcpOption, bytes: &[T], v: &mut Vec<u8>)
1054where
1055 T: Serialise,
1056{
1057 option.serialise(v);
1058 (bytes.len() as u8).serialise(v);
1059 for i in bytes.iter() {
1060 i.serialise(v);
1061 }
1062}
1063
1064impl Serialise for DhcpOptions {
1065 fn serialise(&self, v: &mut Vec<u8>) {
1066 for (o, p) in self.other.iter() {
1067 serialise_option(*o, p, v);
1068 }
1069
1070 (255_u8).serialise(v);
1072 }
1073}
1074
1075fn serialise_fixed(out: &[u8], l: usize, v: &mut Vec<u8>) {
1076 let mut bytes = Vec::with_capacity(l);
1077 bytes.extend_from_slice(out);
1078 bytes.resize_with(l, Default::default);
1079 for b in &bytes {
1080 b.serialise(v);
1081 }
1082}
1083
1084impl Dhcp {
1085 pub fn serialise(&self) -> Vec<u8> {
1086 let mut v: Vec<u8> = Vec::new();
1087 self.op.0.serialise(&mut v);
1088 self.htype.0.serialise(&mut v);
1089 self.hlen.serialise(&mut v);
1090 self.hops.serialise(&mut v);
1091 self.xid.serialise(&mut v);
1092 self.secs.serialise(&mut v);
1093 self.flags.serialise(&mut v);
1094 self.ciaddr.serialise(&mut v);
1095 self.yiaddr.serialise(&mut v);
1096 self.siaddr.serialise(&mut v);
1097 self.giaddr.serialise(&mut v);
1098
1099 serialise_fixed(&self.chaddr, 16, &mut v);
1100 serialise_fixed(&self.sname, 64, &mut v);
1101 serialise_fixed(&self.file, 128, &mut v);
1102
1103 0x6382_5363_u32.serialise(&mut v);
1105
1106 self.options.serialise(&mut v);
1107
1108 v
1109 }
1110
1111 pub fn get_client_id(&self) -> Vec<u8> {
1112 self.options
1113 .get_clientid()
1114 .unwrap_or_else(|| self.chaddr.clone())
1115 }
1116
1117 pub fn get_broadcast_flag(&self) -> bool {
1118 self.flags & 0b1000_0000 != 0
1119 }
1120}
1121
1122#[cfg(test)]
1123fn serialise_one_for_test(opt: DhcpOptionTypeValue) -> Vec<u8> {
1124 let mut v = vec![];
1125 opt.serialise(&mut v);
1126 v
1127}
1128
1129#[test]
1130fn test_type_serialisation() {
1131 assert_eq!(
1132 serialise_one_for_test(DhcpOptionTypeValue::String("test".into())),
1133 vec![116, 101, 115, 116]
1134 );
1135 assert_eq!(
1136 serialise_one_for_test(DhcpOptionTypeValue::Ip("192.0.2.0".parse().unwrap())),
1137 vec![192, 0, 2, 0]
1138 );
1139 assert_eq!(
1140 serialise_one_for_test(DhcpOptionTypeValue::I32(16909060i32)),
1141 vec![1, 2, 3, 4]
1142 );
1143 assert_eq!(
1144 serialise_one_for_test(DhcpOptionTypeValue::U8(42)),
1145 vec![42]
1146 );
1147 assert_eq!(
1148 serialise_one_for_test(DhcpOptionTypeValue::U16(258)),
1149 vec![1, 2],
1150 );
1151 assert_eq!(
1152 serialise_one_for_test(DhcpOptionTypeValue::U32(16909060)),
1153 vec![1, 2, 3, 4]
1154 );
1155 assert_eq!(
1156 serialise_one_for_test(DhcpOptionTypeValue::HwAddr(vec![1, 2, 3, 4, 5, 6])),
1157 vec![1, 2, 3, 4, 5, 6]
1158 );
1159 assert_eq!(
1160 serialise_one_for_test(DhcpOptionTypeValue::IpList(vec![
1161 "192.0.2.0".parse().unwrap(),
1162 "192.0.2.1".parse().unwrap(),
1163 "192.0.2.2".parse().unwrap(),
1164 ])),
1165 vec![192, 0, 2, 0, 192, 0, 2, 1, 192, 0, 2, 2]
1166 );
1167 assert_eq!(
1168 serialise_one_for_test(DhcpOptionTypeValue::Routes(vec![Route {
1169 prefix: erbium_net::Ipv4Subnet::new("192.0.2.0".parse().unwrap(), 24).unwrap(),
1170 nexthop: "192.0.2.254".parse().unwrap(),
1171 }])),
1172 vec![24, 192, 0, 2, 0, 192, 0, 2, 254]
1173 );
1174}
1175
1176#[test]
1177fn test_parse() {
1178 assert_eq!(
1179 format!(
1180 "{}",
1181 DhcpOptionType::String
1182 .decode(&[116, 101, 115, 116])
1183 .unwrap()
1184 ),
1185 "test"
1186 );
1187 assert_eq!(
1188 format!("{}", DhcpOptionType::Ip.decode(&[192, 0, 2, 42]).unwrap()),
1189 "192.0.2.42"
1190 );
1191 assert_eq!(
1192 format!(
1193 "{}",
1194 DhcpOptionType::IpList
1195 .decode(&[192, 0, 2, 12, 192, 0, 2, 17])
1196 .unwrap()
1197 ),
1198 "192.0.2.12,192.0.2.17"
1199 );
1200 assert_eq!(
1201 format!("{}", DhcpOptionType::I32.decode(&[1, 2, 3, 4]).unwrap()),
1202 "16909060",
1203 );
1204 assert_eq!(
1205 format!("{}", DhcpOptionType::U8.decode(&[251]).unwrap()),
1206 "251",
1207 );
1208 assert_eq!(
1209 format!("{}", DhcpOptionType::U16.decode(&[1, 2]).unwrap()),
1210 "258",
1211 );
1212 assert_eq!(
1213 format!("{}", DhcpOptionType::U32.decode(&[1, 2, 3, 4]).unwrap()),
1214 "16909060",
1215 );
1216 assert_eq!(
1217 format!("{}", DhcpOptionType::Bool.decode(&[0]).unwrap()),
1218 "0",
1219 );
1220 assert_eq!(
1221 format!("{}", DhcpOptionType::Bool.decode(&[1]).unwrap()),
1222 "1",
1223 );
1224 assert_eq!(
1225 format!("{}", DhcpOptionType::Seconds16.decode(&[1, 0x2c]).unwrap()),
1226 "300",
1227 );
1228 assert_eq!(
1229 format!(
1230 "{}",
1231 DhcpOptionType::Seconds32
1232 .decode(&[0, 1, 0x51, 0x80])
1233 .unwrap()
1234 ),
1235 "86400",
1236 );
1237 assert_eq!(
1238 format!(
1239 "{}",
1240 DhcpOptionType::HwAddr.decode(&[0, 1, 2, 3, 4, 5]).unwrap()
1241 ),
1242 "00:01:02:03:04:05"
1243 );
1244 assert_eq!(
1245 format!(
1246 "{}",
1247 DhcpOptionType::Routes
1248 .decode(&[
1249 24, 192, 0, 2, 0, 192, 0, 2, 254, 24, 198, 51, 100, 0, 192, 0, 2, 254
1250 ])
1251 .unwrap()
1252 ),
1253 "192.0.2.0/24->192.0.2.254,198.51.100.0/24->192.0.2.254"
1254 );
1255}