1use std::collections::HashMap;
21
22use super::rdata::DnsRData;
23use super::types;
24use crate::layer::field::FieldError;
25use crate::layer::field_ext::DnsName;
26
27#[derive(Debug, Clone, PartialEq)]
29pub struct DnsResourceRecord {
30 pub rrname: DnsName,
32 pub rtype: u16,
34 pub rclass: u16,
37 pub ttl: u32,
39 pub rdata: DnsRData,
41}
42
43impl DnsResourceRecord {
44 pub fn new(rrname: DnsName, rtype: u16, rdata: DnsRData) -> Self {
46 Self {
47 rrname,
48 rtype,
49 rclass: types::dns_class::IN,
50 ttl: 0,
51 rdata,
52 }
53 }
54
55 pub fn parse(packet: &[u8], offset: usize) -> Result<(Self, usize), FieldError> {
62 let (rrname, name_len) = DnsName::decode(packet, offset)?;
64 let fixed_start = offset + name_len;
65
66 if fixed_start + 10 > packet.len() {
68 return Err(FieldError::BufferTooShort {
69 offset: fixed_start,
70 need: 10,
71 have: packet.len().saturating_sub(fixed_start),
72 });
73 }
74
75 let rtype = u16::from_be_bytes([packet[fixed_start], packet[fixed_start + 1]]);
76 let rclass = u16::from_be_bytes([packet[fixed_start + 2], packet[fixed_start + 3]]);
77 let ttl = u32::from_be_bytes([
78 packet[fixed_start + 4],
79 packet[fixed_start + 5],
80 packet[fixed_start + 6],
81 packet[fixed_start + 7],
82 ]);
83 let rdlength = u16::from_be_bytes([packet[fixed_start + 8], packet[fixed_start + 9]]);
84
85 let rdata_start = fixed_start + 10;
86 let rdata_end = rdata_start + rdlength as usize;
87
88 if rdata_end > packet.len() {
90 return Err(FieldError::BufferTooShort {
91 offset: rdata_start,
92 need: rdlength as usize,
93 have: packet.len().saturating_sub(rdata_start),
94 });
95 }
96
97 let rdata = DnsRData::parse(rtype, packet, rdata_start, rdlength)?;
98
99 let total_consumed = name_len + 10 + rdlength as usize;
100 Ok((
101 Self {
102 rrname,
103 rtype,
104 rclass,
105 ttl,
106 rdata,
107 },
108 total_consumed,
109 ))
110 }
111
112 pub fn build(&self) -> Vec<u8> {
114 let rdata_bytes = self.rdata.build();
115 let rdlength = rdata_bytes.len() as u16;
116
117 let mut out = self.rrname.encode();
118 out.extend_from_slice(&self.rtype.to_be_bytes());
119 out.extend_from_slice(&self.rclass.to_be_bytes());
120 out.extend_from_slice(&self.ttl.to_be_bytes());
121 out.extend_from_slice(&rdlength.to_be_bytes());
122 out.extend_from_slice(&rdata_bytes);
123 out
124 }
125
126 pub fn build_compressed(
128 &self,
129 current_offset: usize,
130 compression_map: &mut HashMap<String, u16>,
131 ) -> Vec<u8> {
132 let mut out = self
133 .rrname
134 .encode_compressed(current_offset, compression_map);
135
136 out.extend_from_slice(&self.rtype.to_be_bytes());
137 out.extend_from_slice(&self.rclass.to_be_bytes());
138 out.extend_from_slice(&self.ttl.to_be_bytes());
139
140 let rdata_offset = current_offset + out.len() + 2; let rdata_bytes = self.rdata.build_compressed(rdata_offset, compression_map);
143 let rdlength = rdata_bytes.len() as u16;
144
145 out.extend_from_slice(&rdlength.to_be_bytes());
146 out.extend_from_slice(&rdata_bytes);
147 out
148 }
149
150 pub fn cache_flush(&self) -> bool {
152 self.rclass & 0x8000 != 0
153 }
154
155 pub fn actual_class(&self) -> u16 {
157 self.rclass & 0x7FFF
158 }
159
160 pub fn set_cache_flush(&mut self, flush: bool) {
162 if flush {
163 self.rclass |= 0x8000;
164 } else {
165 self.rclass &= 0x7FFF;
166 }
167 }
168
169 pub fn summary(&self) -> String {
171 let type_name = types::dns_type_name(self.rtype);
172 let class_name = types::dns_class_name(self.actual_class());
173 let rdata_summary = self.rdata.summary();
174 format!(
175 "{} {} {} {}",
176 self.rrname, type_name, class_name, rdata_summary
177 )
178 }
179
180 pub fn is_opt(&self) -> bool {
182 self.rtype == types::rr_type::OPT
183 }
184
185 pub fn opt_udp_size(&self) -> u16 {
191 self.rclass
192 }
193
194 pub fn opt_extended_rcode(&self) -> u8 {
196 ((self.ttl >> 24) & 0xFF) as u8
197 }
198
199 pub fn opt_version(&self) -> u8 {
201 ((self.ttl >> 16) & 0xFF) as u8
202 }
203
204 pub fn opt_do_flag(&self) -> bool {
206 self.ttl & 0x8000 != 0
207 }
208}
209
210impl Default for DnsResourceRecord {
211 fn default() -> Self {
212 Self {
213 rrname: DnsName::root(),
214 rtype: types::rr_type::A,
215 rclass: types::dns_class::IN,
216 ttl: 0,
217 rdata: DnsRData::Unknown {
218 rtype: types::rr_type::A,
219 data: Vec::new(),
220 },
221 }
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use std::net::Ipv4Addr;
229
230 fn build_example_a_record() -> Vec<u8> {
232 let mut data = Vec::new();
233 data.extend_from_slice(&[7, b'e', b'x', b'a', b'm', b'p', b'l', b'e']);
235 data.extend_from_slice(&[3, b'c', b'o', b'm']);
236 data.push(0); data.extend_from_slice(&[0x00, 0x01]);
239 data.extend_from_slice(&[0x00, 0x01]);
241 data.extend_from_slice(&300u32.to_be_bytes());
243 data.extend_from_slice(&[0x00, 0x04]);
245 data.extend_from_slice(&[93, 184, 216, 34]);
247 data
248 }
249
250 #[test]
251 fn test_parse_a_record() {
252 let data = build_example_a_record();
253 let (rr, consumed) = DnsResourceRecord::parse(&data, 0).unwrap();
254
255 assert_eq!(rr.rrname.labels, vec!["example", "com"]);
256 assert_eq!(rr.rtype, types::rr_type::A);
257 assert_eq!(rr.rclass, types::dns_class::IN);
258 assert_eq!(rr.ttl, 300);
259 assert_eq!(rr.rdata, DnsRData::A(Ipv4Addr::new(93, 184, 216, 34)));
260 assert_eq!(consumed, data.len());
261 }
262
263 #[test]
264 fn test_build_roundtrip_a_record() {
265 let rr = DnsResourceRecord {
266 rrname: DnsName::from_str_dotted("example.com").unwrap(),
267 rtype: types::rr_type::A,
268 rclass: types::dns_class::IN,
269 ttl: 3600,
270 rdata: DnsRData::A(Ipv4Addr::new(192, 168, 1, 1)),
271 };
272
273 let built = rr.build();
274 let (parsed, consumed) = DnsResourceRecord::parse(&built, 0).unwrap();
275
276 assert_eq!(consumed, built.len());
277 assert_eq!(parsed.rrname, rr.rrname);
278 assert_eq!(parsed.rtype, rr.rtype);
279 assert_eq!(parsed.rclass, rr.rclass);
280 assert_eq!(parsed.ttl, rr.ttl);
281 assert_eq!(parsed.rdata, rr.rdata);
282 }
283
284 #[test]
285 fn test_build_roundtrip_aaaa_record() {
286 let rr = DnsResourceRecord {
287 rrname: DnsName::from_str_dotted("ipv6.example.com").unwrap(),
288 rtype: types::rr_type::AAAA,
289 rclass: types::dns_class::IN,
290 ttl: 7200,
291 rdata: DnsRData::AAAA("2001:db8::1".parse().unwrap()),
292 };
293
294 let built = rr.build();
295 let (parsed, consumed) = DnsResourceRecord::parse(&built, 0).unwrap();
296
297 assert_eq!(consumed, built.len());
298 assert_eq!(parsed.rtype, types::rr_type::AAAA);
299 assert_eq!(parsed.rdata, rr.rdata);
300 }
301
302 #[test]
303 fn test_build_roundtrip_cname_record() {
304 let rr = DnsResourceRecord {
305 rrname: DnsName::from_str_dotted("www.example.com").unwrap(),
306 rtype: types::rr_type::CNAME,
307 rclass: types::dns_class::IN,
308 ttl: 600,
309 rdata: DnsRData::CNAME(DnsName::from_str_dotted("example.com").unwrap()),
310 };
311
312 let built = rr.build();
313 let (parsed, consumed) = DnsResourceRecord::parse(&built, 0).unwrap();
314
315 assert_eq!(consumed, built.len());
316 assert_eq!(parsed.rtype, types::rr_type::CNAME);
317 assert_eq!(
318 parsed.rdata,
319 DnsRData::CNAME(DnsName::from_str_dotted("example.com").unwrap())
320 );
321 }
322
323 #[test]
324 fn test_build_roundtrip_mx_record() {
325 let rr = DnsResourceRecord {
326 rrname: DnsName::from_str_dotted("example.com").unwrap(),
327 rtype: types::rr_type::MX,
328 rclass: types::dns_class::IN,
329 ttl: 3600,
330 rdata: DnsRData::MX {
331 preference: 10,
332 exchange: DnsName::from_str_dotted("mail.example.com").unwrap(),
333 },
334 };
335
336 let built = rr.build();
337 let (parsed, consumed) = DnsResourceRecord::parse(&built, 0).unwrap();
338
339 assert_eq!(consumed, built.len());
340 assert_eq!(parsed.rtype, types::rr_type::MX);
341 assert_eq!(parsed.rdata, rr.rdata);
342 }
343
344 #[test]
345 fn test_build_roundtrip_txt_record() {
346 let rr = DnsResourceRecord {
347 rrname: DnsName::from_str_dotted("example.com").unwrap(),
348 rtype: types::rr_type::TXT,
349 rclass: types::dns_class::IN,
350 ttl: 300,
351 rdata: DnsRData::TXT(vec![b"v=spf1 include:example.com ~all".to_vec()]),
352 };
353
354 let built = rr.build();
355 let (parsed, consumed) = DnsResourceRecord::parse(&built, 0).unwrap();
356
357 assert_eq!(consumed, built.len());
358 assert_eq!(parsed.rdata, rr.rdata);
359 }
360
361 #[test]
362 fn test_opt_record_helpers() {
363 let rr = DnsResourceRecord {
369 rrname: DnsName::root(),
370 rtype: types::rr_type::OPT,
371 rclass: 4096, ttl: 0x0000_8000,
375 rdata: DnsRData::OPT(vec![]),
376 };
377
378 assert!(rr.is_opt());
379 assert_eq!(rr.opt_udp_size(), 4096);
380 assert_eq!(rr.opt_extended_rcode(), 0);
381 assert_eq!(rr.opt_version(), 0);
382 assert!(rr.opt_do_flag());
383 }
384
385 #[test]
386 fn test_opt_record_extended_rcode_and_version() {
387 let rr = DnsResourceRecord {
390 rrname: DnsName::root(),
391 rtype: types::rr_type::OPT,
392 rclass: 1232,
393 ttl: 0x03_01_0000,
394 rdata: DnsRData::OPT(vec![]),
395 };
396
397 assert!(rr.is_opt());
398 assert_eq!(rr.opt_udp_size(), 1232);
399 assert_eq!(rr.opt_extended_rcode(), 3);
400 assert_eq!(rr.opt_version(), 1);
401 assert!(!rr.opt_do_flag());
402 }
403
404 #[test]
405 fn test_mdns_cache_flush() {
406 let mut rr = DnsResourceRecord::new(
407 DnsName::from_str_dotted("test.local").unwrap(),
408 types::rr_type::A,
409 DnsRData::A(Ipv4Addr::new(192, 168, 0, 1)),
410 );
411
412 assert!(!rr.cache_flush());
413 assert_eq!(rr.actual_class(), types::dns_class::IN);
414
415 rr.set_cache_flush(true);
416 assert!(rr.cache_flush());
417 assert_eq!(rr.actual_class(), types::dns_class::IN);
418 assert_eq!(rr.rclass, 0x8001);
419
420 rr.set_cache_flush(false);
421 assert!(!rr.cache_flush());
422 assert_eq!(rr.rclass, types::dns_class::IN);
423 }
424
425 #[test]
426 fn test_buffer_too_short_no_fixed_fields() {
427 let data = vec![4, b't', b'e', b's', b't', 0];
429 let result = DnsResourceRecord::parse(&data, 0);
430 assert!(result.is_err());
431 match result.unwrap_err() {
432 FieldError::BufferTooShort { need, .. } => assert_eq!(need, 10),
433 other => panic!("Expected BufferTooShort, got {:?}", other),
434 }
435 }
436
437 #[test]
438 fn test_buffer_too_short_truncated_rdata() {
439 let mut data = Vec::new();
440 data.extend_from_slice(&[4, b't', b'e', b's', b't', 0]);
442 data.extend_from_slice(&[0x00, 0x01]);
444 data.extend_from_slice(&[0x00, 0x01]);
446 data.extend_from_slice(&[0x00, 0x00, 0x00, 0x3C]);
448 data.extend_from_slice(&[0x00, 0x04]);
450 data.extend_from_slice(&[192, 168]);
452
453 let result = DnsResourceRecord::parse(&data, 0);
454 assert!(result.is_err());
455 match result.unwrap_err() {
456 FieldError::BufferTooShort { need, have, .. } => {
457 assert_eq!(need, 4);
458 assert_eq!(have, 2);
459 },
460 other => panic!("Expected BufferTooShort, got {:?}", other),
461 }
462 }
463
464 #[test]
465 fn test_buffer_too_short_empty() {
466 let result = DnsResourceRecord::parse(&[], 0);
467 assert!(result.is_err());
468 }
469
470 #[test]
471 fn test_new_defaults() {
472 let rr = DnsResourceRecord::new(
473 DnsName::from_str_dotted("example.com").unwrap(),
474 types::rr_type::A,
475 DnsRData::A(Ipv4Addr::new(1, 2, 3, 4)),
476 );
477
478 assert_eq!(rr.rclass, types::dns_class::IN);
479 assert_eq!(rr.ttl, 0);
480 }
481
482 #[test]
483 fn test_default() {
484 let rr = DnsResourceRecord::default();
485 assert!(rr.rrname.is_root());
486 assert_eq!(rr.rtype, types::rr_type::A);
487 assert_eq!(rr.rclass, types::dns_class::IN);
488 assert_eq!(rr.ttl, 0);
489 }
490
491 #[test]
492 fn test_summary() {
493 let rr = DnsResourceRecord {
494 rrname: DnsName::from_str_dotted("example.com").unwrap(),
495 rtype: types::rr_type::A,
496 rclass: types::dns_class::IN,
497 ttl: 300,
498 rdata: DnsRData::A(Ipv4Addr::new(192, 168, 1, 1)),
499 };
500
501 let summary = rr.summary();
502 assert!(summary.contains("example.com."));
503 assert!(summary.contains("A"));
504 assert!(summary.contains("IN"));
505 assert!(summary.contains("192.168.1.1"));
506 }
507
508 #[test]
509 fn test_is_opt() {
510 let opt = DnsResourceRecord {
511 rrname: DnsName::root(),
512 rtype: types::rr_type::OPT,
513 rclass: 4096,
514 ttl: 0,
515 rdata: DnsRData::OPT(vec![]),
516 };
517 assert!(opt.is_opt());
518
519 let a = DnsResourceRecord::new(
520 DnsName::from_str_dotted("example.com").unwrap(),
521 types::rr_type::A,
522 DnsRData::A(Ipv4Addr::LOCALHOST),
523 );
524 assert!(!a.is_opt());
525 }
526
527 #[test]
528 fn test_parse_at_nonzero_offset() {
529 let record_bytes = build_example_a_record();
531 let mut data = vec![0xDE, 0xAD, 0xBE, 0xEF]; data.extend_from_slice(&record_bytes);
533
534 let (rr, consumed) = DnsResourceRecord::parse(&data, 4).unwrap();
535 assert_eq!(rr.rrname.labels, vec!["example", "com"]);
536 assert_eq!(rr.rdata, DnsRData::A(Ipv4Addr::new(93, 184, 216, 34)));
537 assert_eq!(consumed, record_bytes.len());
538 }
539
540 #[test]
541 fn test_build_compressed_produces_valid_output() {
542 let rr = DnsResourceRecord {
543 rrname: DnsName::from_str_dotted("example.com").unwrap(),
544 rtype: types::rr_type::A,
545 rclass: types::dns_class::IN,
546 ttl: 60,
547 rdata: DnsRData::A(Ipv4Addr::new(10, 0, 0, 1)),
548 };
549
550 let mut compression_map = HashMap::new();
551 let built = rr.build_compressed(0, &mut compression_map);
552
553 let (parsed, consumed) = DnsResourceRecord::parse(&built, 0).unwrap();
556 assert_eq!(consumed, built.len());
557 assert_eq!(parsed.rrname, rr.rrname);
558 assert_eq!(parsed.rdata, rr.rdata);
559 }
560
561 #[test]
562 fn test_build_compressed_reuses_names() {
563 let rr1 = DnsResourceRecord {
564 rrname: DnsName::from_str_dotted("example.com").unwrap(),
565 rtype: types::rr_type::A,
566 rclass: types::dns_class::IN,
567 ttl: 60,
568 rdata: DnsRData::A(Ipv4Addr::new(10, 0, 0, 1)),
569 };
570
571 let rr2 = DnsResourceRecord {
572 rrname: DnsName::from_str_dotted("example.com").unwrap(),
573 rtype: types::rr_type::A,
574 rclass: types::dns_class::IN,
575 ttl: 60,
576 rdata: DnsRData::A(Ipv4Addr::new(10, 0, 0, 2)),
577 };
578
579 let mut compression_map = HashMap::new();
580 let built1 = rr1.build_compressed(0, &mut compression_map);
581 let built2 = rr2.build_compressed(built1.len(), &mut compression_map);
582
583 let uncompressed2 = rr2.build();
585 assert!(built2.len() < uncompressed2.len());
586
587 let mut packet = Vec::new();
589 packet.extend_from_slice(&built1);
590 packet.extend_from_slice(&built2);
591
592 let (parsed1, consumed1) = DnsResourceRecord::parse(&packet, 0).unwrap();
593 assert_eq!(parsed1.rrname.labels, vec!["example", "com"]);
594
595 let (parsed2, _consumed2) = DnsResourceRecord::parse(&packet, consumed1).unwrap();
596 assert_eq!(parsed2.rrname.labels, vec!["example", "com"]);
597 assert_eq!(parsed2.rdata, DnsRData::A(Ipv4Addr::new(10, 0, 0, 2)));
598 }
599
600 #[test]
601 fn test_parse_with_name_pointer() {
602 let mut packet = Vec::new();
604 packet.extend_from_slice(&[7, b'e', b'x', b'a', b'm', b'p', b'l', b'e']);
606 packet.extend_from_slice(&[3, b'c', b'o', b'm']);
607 packet.push(0); packet.extend_from_slice(&[0xC0, 0x00]); packet.extend_from_slice(&[0x00, 0x01]); packet.extend_from_slice(&[0x00, 0x01]); packet.extend_from_slice(&120u32.to_be_bytes()); packet.extend_from_slice(&[0x00, 0x04]); packet.extend_from_slice(&[10, 20, 30, 40]); let (rr, consumed) = DnsResourceRecord::parse(&packet, 13).unwrap();
618 assert_eq!(rr.rrname.labels, vec!["example", "com"]);
619 assert_eq!(rr.rtype, types::rr_type::A);
620 assert_eq!(rr.ttl, 120);
621 assert_eq!(rr.rdata, DnsRData::A(Ipv4Addr::new(10, 20, 30, 40)));
622 assert_eq!(consumed, 16);
624 }
625}