1pub mod builder;
2pub mod options;
3
4use std::net::Ipv4Addr;
5
6use crate::layer::field::{FieldError, FieldValue, MacAddress};
7use crate::layer::{Layer, LayerIndex, LayerKind};
8
9use self::options::{DhcpOption, code, parse_options};
10
11pub use builder::DhcpBuilder;
12
13const MAGIC_COOKIE: [u8; 4] = [99, 130, 83, 99];
15
16pub const DHCP_MIN_HEADER_LEN: usize = 240;
18
19pub const DHCP_SERVER_PORT: u16 = 67;
21pub const DHCP_CLIENT_PORT: u16 = 68;
22
23pub const DHCP_FIELD_NAMES: &[&str] = &[
25 "op",
26 "htype",
27 "hlen",
28 "hops",
29 "xid",
30 "secs",
31 "flags",
32 "ciaddr",
33 "yiaddr",
34 "siaddr",
35 "giaddr",
36 "chaddr",
37 "msg_type",
38 "server_id",
39 "requested_ip",
40 "lease_time",
41 "subnet_mask",
42 "router",
43 "dns",
44];
45
46fn short(need: usize, have: usize) -> FieldError {
47 FieldError::BufferTooShort {
48 offset: 0,
49 need,
50 have,
51 }
52}
53
54#[derive(Debug, Clone)]
56pub struct DhcpLayer {
57 pub index: LayerIndex,
58}
59
60impl DhcpLayer {
61 #[must_use]
62 pub fn new(index: LayerIndex) -> Self {
63 Self { index }
64 }
65
66 fn data<'a>(&self, buf: &'a [u8]) -> &'a [u8] {
67 &buf[self.index.start..self.index.end]
68 }
69
70 fn check<'a>(&self, buf: &'a [u8], need: usize) -> Result<&'a [u8], FieldError> {
71 let d = self.data(buf);
72 if d.len() < need {
73 Err(short(need, d.len()))
74 } else {
75 Ok(d)
76 }
77 }
78
79 pub fn op(&self, buf: &[u8]) -> Result<u8, FieldError> {
81 Ok(self.check(buf, 1)?[0])
82 }
83
84 pub fn htype(&self, buf: &[u8]) -> Result<u8, FieldError> {
86 Ok(self.check(buf, 2)?[1])
87 }
88
89 pub fn hlen(&self, buf: &[u8]) -> Result<u8, FieldError> {
91 Ok(self.check(buf, 3)?[2])
92 }
93
94 pub fn hops(&self, buf: &[u8]) -> Result<u8, FieldError> {
96 Ok(self.check(buf, 4)?[3])
97 }
98
99 pub fn xid(&self, buf: &[u8]) -> Result<u32, FieldError> {
101 let d = self.check(buf, 8)?;
102 Ok(u32::from_be_bytes([d[4], d[5], d[6], d[7]]))
103 }
104
105 pub fn secs(&self, buf: &[u8]) -> Result<u16, FieldError> {
107 let d = self.check(buf, 10)?;
108 Ok(u16::from_be_bytes([d[8], d[9]]))
109 }
110
111 pub fn flags(&self, buf: &[u8]) -> Result<u16, FieldError> {
113 let d = self.check(buf, 12)?;
114 Ok(u16::from_be_bytes([d[10], d[11]]))
115 }
116
117 pub fn ciaddr(&self, buf: &[u8]) -> Result<Ipv4Addr, FieldError> {
119 let d = self.check(buf, 16)?;
120 Ok(Ipv4Addr::new(d[12], d[13], d[14], d[15]))
121 }
122
123 pub fn yiaddr(&self, buf: &[u8]) -> Result<Ipv4Addr, FieldError> {
125 let d = self.check(buf, 20)?;
126 Ok(Ipv4Addr::new(d[16], d[17], d[18], d[19]))
127 }
128
129 pub fn siaddr(&self, buf: &[u8]) -> Result<Ipv4Addr, FieldError> {
131 let d = self.check(buf, 24)?;
132 Ok(Ipv4Addr::new(d[20], d[21], d[22], d[23]))
133 }
134
135 pub fn giaddr(&self, buf: &[u8]) -> Result<Ipv4Addr, FieldError> {
137 let d = self.check(buf, 28)?;
138 Ok(Ipv4Addr::new(d[24], d[25], d[26], d[27]))
139 }
140
141 pub fn chaddr(&self, buf: &[u8]) -> Result<[u8; 6], FieldError> {
143 let d = self.check(buf, 34)?;
144 let mut mac = [0u8; 6];
145 mac.copy_from_slice(&d[28..34]);
146 Ok(mac)
147 }
148
149 pub fn options(&self, buf: &[u8]) -> Vec<DhcpOption> {
151 let d = self.data(buf);
152 if d.len() < 240 {
153 return Vec::new();
154 }
155 if d[236..240] != MAGIC_COOKIE {
156 return Vec::new();
157 }
158 parse_options(&d[240..])
159 }
160
161 pub fn get_option(&self, buf: &[u8], opt_code: u8) -> Option<DhcpOption> {
163 self.options(buf).into_iter().find(|o| o.code == opt_code)
164 }
165
166 pub fn msg_type(&self, buf: &[u8]) -> Option<u8> {
168 self.get_option(buf, code::MESSAGE_TYPE)
169 .and_then(|o| o.as_message_type())
170 }
171
172 pub fn server_id(&self, buf: &[u8]) -> Option<Ipv4Addr> {
174 self.get_option(buf, code::SERVER_ID)
175 .and_then(|o| o.as_ipv4())
176 }
177
178 pub fn requested_ip(&self, buf: &[u8]) -> Option<Ipv4Addr> {
180 self.get_option(buf, code::REQUESTED_IP)
181 .and_then(|o| o.as_ipv4())
182 }
183
184 pub fn lease_time(&self, buf: &[u8]) -> Option<u32> {
186 self.get_option(buf, code::LEASE_TIME).and_then(|o| {
187 if o.data.len() >= 4 {
188 Some(u32::from_be_bytes([
189 o.data[0], o.data[1], o.data[2], o.data[3],
190 ]))
191 } else {
192 None
193 }
194 })
195 }
196
197 pub fn subnet_mask(&self, buf: &[u8]) -> Option<Ipv4Addr> {
199 self.get_option(buf, code::SUBNET_MASK)
200 .and_then(|o| o.as_ipv4())
201 }
202
203 pub fn router(&self, buf: &[u8]) -> Option<Ipv4Addr> {
205 self.get_option(buf, code::ROUTER).and_then(|o| o.as_ipv4())
206 }
207
208 pub fn dns(&self, buf: &[u8]) -> Vec<Ipv4Addr> {
210 self.get_option(buf, code::DNS)
211 .map(|o| {
212 o.data
213 .chunks_exact(4)
214 .map(|c| Ipv4Addr::new(c[0], c[1], c[2], c[3]))
215 .collect()
216 })
217 .unwrap_or_default()
218 }
219
220 pub fn is_request(&self, buf: &[u8]) -> bool {
222 self.op(buf).is_ok_and(|op| op == 1)
223 }
224
225 pub fn is_reply(&self, buf: &[u8]) -> bool {
227 self.op(buf).is_ok_and(|op| op == 2)
228 }
229
230 pub fn set_op(&self, buf: &mut [u8], val: u8) -> Result<(), FieldError> {
232 let start = self.index.start;
233 let d = &mut buf[start..self.index.end];
234 if d.is_empty() {
235 return Err(short(1, 0));
236 }
237 d[0] = val;
238 Ok(())
239 }
240
241 pub fn set_xid(&self, buf: &mut [u8], val: u32) -> Result<(), FieldError> {
243 let start = self.index.start;
244 let d = &mut buf[start..self.index.end];
245 if d.len() < 8 {
246 return Err(short(8, d.len()));
247 }
248 d[4..8].copy_from_slice(&val.to_be_bytes());
249 Ok(())
250 }
251
252 pub fn set_flags(&self, buf: &mut [u8], val: u16) -> Result<(), FieldError> {
254 let start = self.index.start;
255 let d = &mut buf[start..self.index.end];
256 if d.len() < 12 {
257 return Err(short(12, d.len()));
258 }
259 d[10..12].copy_from_slice(&val.to_be_bytes());
260 Ok(())
261 }
262
263 pub fn get_field(&self, buf: &[u8], name: &str) -> Option<Result<FieldValue, FieldError>> {
265 match name {
266 "op" => Some(self.op(buf).map(|v| FieldValue::U8(v))),
267 "htype" => Some(self.htype(buf).map(|v| FieldValue::U8(v))),
268 "hlen" => Some(self.hlen(buf).map(|v| FieldValue::U8(v))),
269 "hops" => Some(self.hops(buf).map(|v| FieldValue::U8(v))),
270 "xid" => Some(self.xid(buf).map(|v| FieldValue::U32(v))),
271 "secs" => Some(self.secs(buf).map(|v| FieldValue::U16(v))),
272 "flags" => Some(self.flags(buf).map(|v| FieldValue::U16(v))),
273 "ciaddr" => Some(self.ciaddr(buf).map(|v| FieldValue::Ipv4(v))),
274 "yiaddr" => Some(self.yiaddr(buf).map(|v| FieldValue::Ipv4(v))),
275 "siaddr" => Some(self.siaddr(buf).map(|v| FieldValue::Ipv4(v))),
276 "giaddr" => Some(self.giaddr(buf).map(|v| FieldValue::Ipv4(v))),
277 "chaddr" => Some(
278 self.chaddr(buf)
279 .map(|mac| FieldValue::Mac(MacAddress::new(mac))),
280 ),
281 "msg_type" => Some(Ok(match self.msg_type(buf) {
282 Some(v) => FieldValue::U8(v),
283 None => FieldValue::U8(0),
284 })),
285 "server_id" => Some(Ok(match self.server_id(buf) {
286 Some(v) => FieldValue::Ipv4(v),
287 None => FieldValue::Ipv4(Ipv4Addr::UNSPECIFIED),
288 })),
289 "requested_ip" => Some(Ok(match self.requested_ip(buf) {
290 Some(v) => FieldValue::Ipv4(v),
291 None => FieldValue::Ipv4(Ipv4Addr::UNSPECIFIED),
292 })),
293 "lease_time" => Some(Ok(FieldValue::U32(self.lease_time(buf).unwrap_or(0)))),
294 "subnet_mask" => Some(Ok(match self.subnet_mask(buf) {
295 Some(v) => FieldValue::Ipv4(v),
296 None => FieldValue::Ipv4(Ipv4Addr::UNSPECIFIED),
297 })),
298 "router" => Some(Ok(match self.router(buf) {
299 Some(v) => FieldValue::Ipv4(v),
300 None => FieldValue::Ipv4(Ipv4Addr::UNSPECIFIED),
301 })),
302 "dns" => {
303 let servers = self.dns(buf);
304 if servers.is_empty() {
305 Some(Ok(FieldValue::Str(String::new())))
306 } else {
307 let s = servers
308 .iter()
309 .map(|ip| ip.to_string())
310 .collect::<Vec<_>>()
311 .join(",");
312 Some(Ok(FieldValue::Str(s)))
313 }
314 },
315 _ => None,
316 }
317 }
318
319 pub fn set_field(
321 &self,
322 buf: &mut [u8],
323 name: &str,
324 value: FieldValue,
325 ) -> Option<Result<(), FieldError>> {
326 match name {
327 "op" => {
328 if let FieldValue::U8(v) = value {
329 Some(self.set_op(buf, v))
330 } else {
331 Some(Err(FieldError::InvalidValue(format!(
332 "op: expected U8, got {value:?}"
333 ))))
334 }
335 },
336 "xid" => {
337 if let FieldValue::U32(v) = value {
338 Some(self.set_xid(buf, v))
339 } else {
340 Some(Err(FieldError::InvalidValue(format!(
341 "xid: expected U32, got {value:?}"
342 ))))
343 }
344 },
345 "flags" => {
346 if let FieldValue::U16(v) = value {
347 Some(self.set_flags(buf, v))
348 } else {
349 Some(Err(FieldError::InvalidValue(format!(
350 "flags: expected U16, got {value:?}"
351 ))))
352 }
353 },
354 _ => None,
355 }
356 }
357
358 pub fn field_names(&self) -> &'static [&'static str] {
360 DHCP_FIELD_NAMES
361 }
362}
363
364impl Layer for DhcpLayer {
365 fn kind(&self) -> LayerKind {
366 LayerKind::Dhcp
367 }
368
369 fn summary(&self, buf: &[u8]) -> String {
370 let msg = match self.msg_type(buf) {
371 Some(1) => "Discover",
372 Some(2) => "Offer",
373 Some(3) => "Request",
374 Some(4) => "Decline",
375 Some(5) => "ACK",
376 Some(6) => "NAK",
377 Some(7) => "Release",
378 Some(8) => "Inform",
379 _ => "Unknown",
380 };
381 format!("DHCP {msg}")
382 }
383
384 fn header_len(&self, buf: &[u8]) -> usize {
385 let d = self.data(buf);
386 d.len()
387 }
388
389 fn field_names(&self) -> &'static [&'static str] {
390 DHCP_FIELD_NAMES
391 }
392}
393
394#[must_use]
396pub fn is_dhcp_payload(data: &[u8]) -> bool {
397 if data.len() < 240 {
398 return false;
399 }
400 data[236..240] == MAGIC_COOKIE
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406 use crate::layer::field::MacAddress;
407
408 #[test]
409 fn test_parse_discover() {
410 let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
411 let data = DhcpBuilder::discover(mac, 0xaabbccdd).build();
412
413 let layer = DhcpLayer::new(LayerIndex {
414 kind: LayerKind::Dhcp,
415 start: 0,
416 end: data.len(),
417 });
418
419 assert_eq!(layer.op(&data).unwrap(), 1);
420 assert_eq!(layer.xid(&data).unwrap(), 0xaabbccdd);
421 assert_eq!(
422 layer.chaddr(&data).unwrap(),
423 [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]
424 );
425 assert_eq!(layer.msg_type(&data), Some(options::msg_type::DISCOVER));
426 assert_eq!(layer.summary(&data), "DHCP Discover");
427 }
428
429 #[test]
430 fn test_parse_offer() {
431 let mac = MacAddress::new([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
432 let data = DhcpBuilder::offer(
433 0x12345678,
434 mac,
435 Ipv4Addr::new(192, 168, 1, 100),
436 Ipv4Addr::new(192, 168, 1, 1),
437 )
438 .lease_time(7200)
439 .subnet_mask(Ipv4Addr::new(255, 255, 255, 0))
440 .router(Ipv4Addr::new(192, 168, 1, 1))
441 .dns(&[Ipv4Addr::new(8, 8, 8, 8)])
442 .build();
443
444 let layer = DhcpLayer::new(LayerIndex {
445 kind: LayerKind::Dhcp,
446 start: 0,
447 end: data.len(),
448 });
449
450 assert_eq!(layer.op(&data).unwrap(), 2);
451 assert_eq!(
452 layer.yiaddr(&data).unwrap(),
453 Ipv4Addr::new(192, 168, 1, 100)
454 );
455 assert_eq!(layer.siaddr(&data).unwrap(), Ipv4Addr::new(192, 168, 1, 1));
456 assert_eq!(layer.msg_type(&data), Some(options::msg_type::OFFER));
457 assert_eq!(layer.server_id(&data), Some(Ipv4Addr::new(192, 168, 1, 1)));
458 assert_eq!(layer.summary(&data), "DHCP Offer");
459 }
460
461 #[test]
462 fn test_is_dhcp_payload() {
463 let mac = MacAddress::new([0x00; 6]);
464 let data = DhcpBuilder::discover(mac, 1).build();
465 assert!(is_dhcp_payload(&data));
466 assert!(!is_dhcp_payload(&[0u8; 10]));
467 }
468}