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