1 // Copyright 2024 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 /// This module parses DNS response records and extracts fully
16 /// qualified domain names (FQDNs) along with their corresponding
17 /// IP Addresses (IpAddr).
18 ///
19 /// **Note:** This is not a general-purpose DNS response parser. It is
20 /// designed to handle specific record types and response formats.
21 use std::convert::TryFrom;
22 use std::fmt;
23 use std::io::{Cursor, Read, Seek, SeekFrom};
24 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
25 use std::str;
26 #[allow(unused_imports)]
27 use std::str::FromStr;
28
29 // REGION CURSOR
30
31 /// Extension trait providing convenient methods for reading primitive
32 /// data types used by DNS messages from a `Cursor<&[u8]>`.
33
34 trait CursorExt: Read + Seek + Clone {
read_u8(&mut self) -> std::io::Result<u8>35 fn read_u8(&mut self) -> std::io::Result<u8>;
read_u16(&mut self) -> std::io::Result<u16>36 fn read_u16(&mut self) -> std::io::Result<u16>;
read_u32(&mut self) -> std::io::Result<u32>37 fn read_u32(&mut self) -> std::io::Result<u32>;
read_ipv4addr(&mut self) -> std::io::Result<Ipv4Addr>38 fn read_ipv4addr(&mut self) -> std::io::Result<Ipv4Addr>;
read_ipv6addr(&mut self) -> std::io::Result<Ipv6Addr>39 fn read_ipv6addr(&mut self) -> std::io::Result<Ipv6Addr>;
get_ref(&self) -> &[u8]40 fn get_ref(&self) -> &[u8];
position(&self) -> u6441 fn position(&self) -> u64;
set_position(&mut self, pos: u64)42 fn set_position(&mut self, pos: u64);
43 }
44
45 impl CursorExt for Cursor<&[u8]> {
read_u8(&mut self) -> std::io::Result<u8>46 fn read_u8(&mut self) -> std::io::Result<u8> {
47 let mut buf = [0; 1];
48 self.read_exact(&mut buf)?;
49 Ok(buf[0])
50 }
51
read_u16(&mut self) -> std::io::Result<u16>52 fn read_u16(&mut self) -> std::io::Result<u16> {
53 let mut buf = [0; 2];
54 self.read_exact(&mut buf)?;
55 Ok(u16::from_be_bytes(buf))
56 }
57
read_u32(&mut self) -> std::io::Result<u32>58 fn read_u32(&mut self) -> std::io::Result<u32> {
59 let mut buf = [0; 4];
60 self.read_exact(&mut buf)?;
61 Ok(u32::from_be_bytes(buf))
62 }
63
read_ipv4addr(&mut self) -> std::io::Result<Ipv4Addr>64 fn read_ipv4addr(&mut self) -> std::io::Result<Ipv4Addr> {
65 let mut buf = [0; 4];
66 self.read_exact(&mut buf)?;
67 Ok(Ipv4Addr::from(buf))
68 }
69
read_ipv6addr(&mut self) -> std::io::Result<Ipv6Addr>70 fn read_ipv6addr(&mut self) -> std::io::Result<Ipv6Addr> {
71 let mut buf = [0; 16];
72 self.read_exact(&mut buf)?;
73 Ok(Ipv6Addr::from(buf))
74 }
75
get_ref(&self) -> &[u8]76 fn get_ref(&self) -> &[u8] {
77 self.get_ref() // Call the original get_ref method
78 }
position(&self) -> u6479 fn position(&self) -> u64 {
80 self.position()
81 }
set_position(&mut self, pos: u64)82 fn set_position(&mut self, pos: u64) {
83 self.set_position(pos)
84 }
85 }
86
87 // END REGION CURSOR
88
89 // REGION MESSAGE
90
91 /// '''
92 /// +---------------------+
93 /// | Header |
94 /// +---------------------+
95 /// | Question | the question for the name server
96 /// +---------------------+
97 /// | Answer | RRs answering the question
98 /// +---------------------+
99 /// | Authority | RRs pointing toward an authority
100 /// +---------------------+
101 /// | Additional | RRs holding additional information
102 /// +---------------------+
103 /// '''
104
105 #[derive(Debug)]
106 struct Message {
107 #[allow(dead_code)]
108 header: Header,
109 #[allow(dead_code)]
110 questions: Vec<Question>,
111 answers: Vec<ResourceRecord>,
112 // Other types not needed
113 // Authority
114 // Additional
115 }
116
117 impl Message {
parse(cursor: &mut impl CursorExt) -> Result<Message>118 fn parse(cursor: &mut impl CursorExt) -> Result<Message> {
119 let header = Header::parse(cursor)?;
120
121 // Reject DNS messages that are not responses
122 if !header.response {
123 return Err(DnsError::ResponseExpected);
124 }
125 if header.opcode != Opcode::StandardQuery {
126 return Err(DnsError::StandardQueryExpected);
127 }
128 if header.response_code != ResponseCode::NoError {
129 return Err(DnsError::ResponseCodeExpected);
130 }
131
132 if header.answer_count == 0 {
133 return Err(DnsError::AnswerExpected);
134 }
135
136 let mut questions = Vec::with_capacity(header.question_count);
137 for _i in 0..header.question_count {
138 let question = Question::split_once(cursor)?;
139 questions.push(question);
140 }
141 let mut answers = Vec::with_capacity(header.answer_count);
142 for _i in 0..header.answer_count {
143 let answer = ResourceRecord::split_once(cursor)?;
144 answers.push(answer);
145 }
146 Ok(Message { header, questions, answers })
147 }
148 }
149
parse_answers(bytes: &[u8]) -> Result<Vec<(IpAddr, String)>>150 pub fn parse_answers(bytes: &[u8]) -> Result<Vec<(IpAddr, String)>> {
151 let mut cursor = Cursor::new(bytes);
152 let msg = Message::parse(&mut cursor)?;
153 let mut responses = Vec::with_capacity(msg.answers.len());
154 for answer in msg.answers {
155 responses.push((answer.resource_data.into(), answer.name));
156 }
157 Ok(responses)
158 }
159
160 // END REGION MESSAGE
161
162 // REGION HEADER
163
164 /// Represents parsed header of the packet.
165 /// The header contains the following fields:
166 /// '''
167 /// 1 1 1 1 1 1
168 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
169 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
170 /// | ID |
171 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
172 /// |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
173 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
174 /// | QDCOUNT |
175 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
176 /// | ANCOUNT |
177 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
178 /// | NSCOUNT |
179 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
180 /// | ARCOUNT |
181 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
182 /// '''
183 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
184 struct Header {
185 /// A 16 bit identifier assigned by the program that
186 /// generates any kind of query. This identifier is copied
187 /// the corresponding reply and can be used by the requester
188 /// to match up replies to outstanding queries.
189 id: u16,
190 /// A one bit field that specifies whether this message is a
191 /// query (0), or a response (1).
192 response: bool,
193 /// A four bit field that specifies kind of query in this
194 /// message. This value is set by the originator of a query
195 /// and copied into the response.
196 opcode: Opcode,
197 response_code: ResponseCode,
198 question_count: usize,
199 answer_count: usize,
200 nameserver_count: usize,
201 additional_count: usize,
202 }
203
204 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
205 enum Opcode {
206 /// Normal query
207 StandardQuery = 0,
208 /// Inverse query (query a name by IP)
209 InverseQuery = 1,
210 /// Server status request
211 ServerStatusRequest = 2,
212 }
213
214 /// The RCODE value according to RFC 1035
215 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
216 enum ResponseCode {
217 NoError,
218 FormatError,
219 ServerFailure,
220 NameError,
221 NotImplemented,
222 Refused,
223 }
224
225 impl TryFrom<u16> for ResponseCode {
226 type Error = DnsError;
227
try_from(value: u16) -> Result<Self>228 fn try_from(value: u16) -> Result<Self> {
229 match value {
230 0 => Ok(ResponseCode::NoError),
231 1 => Ok(ResponseCode::FormatError),
232 2 => Ok(ResponseCode::ServerFailure),
233 3 => Ok(ResponseCode::NameError),
234 4 => Ok(ResponseCode::NotImplemented),
235 5 => Ok(ResponseCode::Refused),
236 _ => Err(DnsError::InvalidResponseCode(value)),
237 }
238 }
239 }
240
241 impl TryFrom<u16> for Opcode {
242 type Error = DnsError;
243
try_from(value: u16) -> Result<Self>244 fn try_from(value: u16) -> Result<Self> {
245 match value {
246 0 => Ok(Opcode::StandardQuery),
247 1 => Ok(Opcode::InverseQuery),
248 2 => Ok(Opcode::ServerStatusRequest),
249 _ => Err(DnsError::InvalidOpcode(value)),
250 }
251 }
252 }
253
254 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
255 struct Flag(u16);
256
257 impl Flag {
258 const RESPONSE: u16 = 0x8000;
259 const OPCODE_MASK: u16 = 0x7800;
260 const RESERVED_MASK: u16 = 0x0004;
261 const RESPONSE_CODE_MASK: u16 = 0x000F;
262
new(value: u16) -> Self263 fn new(value: u16) -> Self {
264 Self(value)
265 }
266
is_set(&self, mask: u16) -> bool267 fn is_set(&self, mask: u16) -> bool {
268 (self.0 & mask) == mask
269 }
270
get(&self, mask: u16) -> u16271 fn get(&self, mask: u16) -> u16 {
272 (self.0 & mask) >> mask.trailing_zeros()
273 }
274 }
275
276 impl Header {
277 /// Parse the header into a header structure
parse(cursor: &mut impl CursorExt) -> Result<Header>278 fn parse(cursor: &mut impl CursorExt) -> Result<Header> {
279 let id = cursor.read_u16()?;
280 let f = cursor.read_u16()?;
281 let question_count = cursor.read_u16()? as usize;
282 let answer_count = cursor.read_u16()? as usize;
283 let nameserver_count = cursor.read_u16()? as usize;
284 let additional_count = cursor.read_u16()? as usize;
285 let flags = Flag::new(f);
286 if flags.get(Flag::RESERVED_MASK) != 0 {
287 return Err(DnsError::ReservedBitsAreNonZero);
288 }
289 let header = Header {
290 id,
291 response: flags.is_set(Flag::RESPONSE),
292 opcode: Opcode::try_from(flags.get(Flag::OPCODE_MASK))?,
293 response_code: ResponseCode::try_from(flags.get(Flag::RESPONSE_CODE_MASK))?,
294 question_count,
295 answer_count,
296 nameserver_count,
297 additional_count,
298 };
299 Ok(header)
300 }
301 }
302
303 // END REGION HEADER
304
305 // REGION QUESTION
306
307 /// 4.1.2. Question section format
308 ///
309 /// The question section is used to carry the "question" in most queries,
310 /// i.e., the parameters that define what is being asked. The section
311 /// contains QDCOUNT (usually 1) entries, each of the following format:
312 /// '''
313 /// 1 1 1 1 1 1
314 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
315 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
316 /// | |
317 /// / QNAME /
318 /// / /
319 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
320 /// | QTYPE |
321 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
322 /// | QCLASS |
323 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
324 /// '''
325
326 #[derive(Debug)]
327 struct Question {
328 #[allow(dead_code)]
329 name: String,
330 #[allow(dead_code)]
331 qtype: u16,
332 #[allow(dead_code)]
333 qclass: u16,
334 }
335
336 impl Question {
split_once(cursor: &mut impl CursorExt) -> Result<Question>337 fn split_once(cursor: &mut impl CursorExt) -> Result<Question> {
338 let name = Name::to_string(cursor)?;
339 let qtype = cursor.read_u16()?;
340 let qclass = cursor.read_u16()?;
341 Ok(Question { name, qtype, qclass })
342 }
343 }
344
345 // END REGION QUESTION
346
347 // REGION RESOURCE RECORD
348
349 /// All RRs have the same top level format shown below:
350 ///
351 /// '''
352 /// 1 1 1 1 1 1
353 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
354 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
355 /// | |
356 /// / /
357 /// / NAME /
358 /// | |
359 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
360 /// | TYPE |
361 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
362 /// | CLASS |
363 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
364 /// | TTL |
365 /// | |
366 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
367 /// | RDLENGTH |
368 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
369 /// / RDATA /
370 /// / /
371 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
372 /// '''
373
374 // DNS resource record classes.
375 //
376 // The only one we care about is Internet
377 #[derive(Debug)]
378 enum ResourceClass {
379 Internet = 1,
380 }
381
382 // Type fields in resource records.
383 //
384 // The only ones we care about are A and AAAA
385 #[derive(Debug)]
386 enum ResourceType {
387 // IPv4 address.
388 A = 1,
389 // IPv6 address, see RFC 3596.
390 Aaaa = 28,
391 }
392
393 #[derive(Debug)]
394 struct ResourceRecord {
395 name: String,
396 #[allow(dead_code)]
397 resource_type: ResourceType,
398 #[allow(dead_code)]
399 resource_class: ResourceClass,
400 #[allow(dead_code)]
401 ttl: u32,
402 resource_data: ResourceData,
403 }
404
405 impl ResourceRecord {
split_once(cursor: &mut impl CursorExt) -> Result<ResourceRecord>406 fn split_once(cursor: &mut impl CursorExt) -> Result<ResourceRecord> {
407 let name = Name::to_string(cursor)?;
408 let rtype = cursor.read_u16()?;
409 let resource_type = match rtype {
410 x if x == ResourceType::A as u16 => ResourceType::A,
411 x if x == ResourceType::Aaaa as u16 => ResourceType::Aaaa,
412 _ => return Err(DnsError::InvalidResourceType),
413 };
414 let rclass = cursor.read_u16()?;
415 let resource_class = match rclass {
416 x if x == ResourceClass::Internet as u16 => ResourceClass::Internet,
417 _ => return Err(DnsError::InvalidResourceClass),
418 };
419 let ttl = cursor.read_u32()?;
420 let _ = cursor.read_u16()?;
421 let resource_data = ResourceData::split_once(cursor, &resource_type)?;
422 Ok(ResourceRecord { name, resource_type, resource_class, ttl, resource_data })
423 }
424 }
425
426 // Only interested in IpAddr resource data
427 #[derive(Debug, PartialEq)]
428 struct ResourceData(IpAddr);
429
430 impl From<ResourceData> for IpAddr {
from(resource_data: ResourceData) -> Self431 fn from(resource_data: ResourceData) -> Self {
432 resource_data.0
433 }
434 }
435
436 impl ResourceData {
split_once( cursor: &mut impl CursorExt, resource_type: &ResourceType, ) -> Result<ResourceData>437 fn split_once(
438 cursor: &mut impl CursorExt,
439 resource_type: &ResourceType,
440 ) -> Result<ResourceData> {
441 match resource_type {
442 ResourceType::A => Ok(ResourceData(cursor.read_ipv4addr()?.into())),
443 ResourceType::Aaaa => Ok(ResourceData(cursor.read_ipv6addr()?.into())),
444 }
445 }
446 }
447
448 // END REGION RESOURCE RECORD
449
450 // REGION LABEL
451
452 type Result<T> = core::result::Result<T, DnsError>;
453
454 #[derive(Debug)]
455 pub enum DnsError {
456 ResponseExpected,
457 StandardQueryExpected,
458 ResponseCodeExpected,
459 AnswerExpected,
460 PointerLoop,
461 InvalidLength,
462 Utf8Error(str::Utf8Error),
463 InvalidResourceType,
464 InvalidResourceClass,
465 AddrParseError(std::net::AddrParseError),
466 InvalidOpcode(u16),
467 InvalidResponseCode(u16),
468 ReservedBitsAreNonZero,
469 IoError(std::io::Error),
470 }
471
472 impl std::error::Error for DnsError {}
473
474 impl fmt::Display for DnsError {
fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result475 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
476 write!(fmt, "{self:?}")
477 }
478 }
479
480 impl From<std::io::Error> for DnsError {
from(err: std::io::Error) -> Self481 fn from(err: std::io::Error) -> Self {
482 DnsError::IoError(err)
483 }
484 }
485 impl From<str::Utf8Error> for DnsError {
from(err: str::Utf8Error) -> Self486 fn from(err: str::Utf8Error) -> Self {
487 DnsError::Utf8Error(err)
488 }
489 }
490
491 impl From<std::net::AddrParseError> for DnsError {
from(err: std::net::AddrParseError) -> Self492 fn from(err: std::net::AddrParseError) -> Self {
493 DnsError::AddrParseError(err)
494 }
495 }
496
497 // REGION NAME
498
499 /// RFC 1035 4.1.4. Message compression
500 ///
501 /// In order to reduce the size of messages, the domain system
502 /// utilizes a compression scheme which eliminates the repetition of
503 /// domain names in a message. In this scheme, an entire domain name
504 /// or a list of labels at the end of a domain name is replaced with a
505 /// pointer to a prior occurrence of the same name.
506 ///
507 /// The pointer takes the form of a two octet sequence:
508 ///
509 /// '''
510 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
511 /// | 1 1| OFFSET |
512 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
513 /// '''
514
515 enum NamePart {
516 Label(String),
517 Pointer(u64),
518 Root,
519 }
520
521 const PTR_MASK: u8 = 0b11000000;
522
523 impl NamePart {
524 /// Domain name labels have a maximum length of 63 octets.
525 const MAX: u8 = 63;
526
527 #[allow(dead_code)]
split_once(cursor: &mut impl CursorExt) -> Result<NamePart>528 fn split_once(cursor: &mut impl CursorExt) -> Result<NamePart> {
529 let size = cursor.read_u8()?;
530 if size & PTR_MASK == PTR_MASK {
531 let two = cursor.read_u8()?;
532 let offset: u64 = u16::from_be_bytes([size & !PTR_MASK, two]).into();
533 return Ok(NamePart::Pointer(offset));
534 }
535 if size == 0 {
536 return Ok(NamePart::Root);
537 }
538 if size > Self::MAX {
539 return Err(DnsError::InvalidLength);
540 }
541 let end = size as usize;
542 let buffer_ref: &[u8] = cursor.get_ref();
543 let start = cursor.position() as usize;
544 let label = str::from_utf8(&buffer_ref[start..start + end])?.to_string();
545 cursor.seek(SeekFrom::Current(end as i64))?;
546 Ok(NamePart::Label(label))
547 }
548 }
549
550 /// The Fully Qualitifed Domain Name from ANSWER and RR records
551
552 struct Name();
553
554 impl Name {
555 // Convert a variable length QNAME or NAME to a String.
556 //
557 // The cursor is updated to the end of the first sequence of
558 // labels, and not the position after a Pointer. This allows the
559 // cursor to be used for reading the remainder of the Question or
560 // ResourceRecord.
561 //
562 // Limit the number of Pointers in malificient messages to avoid
563 // looping.
564 //
to_string(cursor: &mut impl CursorExt) -> Result<String>565 fn to_string(cursor: &mut impl CursorExt) -> Result<String> {
566 Self::to_string_guard(cursor, 0)
567 }
568
to_string_guard(cursor: &mut impl CursorExt, jumps: usize) -> Result<String>569 fn to_string_guard(cursor: &mut impl CursorExt, jumps: usize) -> Result<String> {
570 if jumps > 2 {
571 return Err(DnsError::PointerLoop);
572 }
573 let mut name = String::with_capacity(255);
574 loop {
575 match NamePart::split_once(cursor)? {
576 NamePart::Root => return Ok(name),
577 NamePart::Pointer(offset) => {
578 let mut pointer_cursor = cursor.clone();
579 pointer_cursor.set_position(offset);
580 let pointer_name = Name::to_string_guard(&mut pointer_cursor, jumps + 1)?;
581 name.push_str(&pointer_name);
582 return Ok(name);
583 }
584 NamePart::Label(label) => {
585 if !name.is_empty() {
586 name.push('.');
587 }
588 name.push_str(&label);
589 }
590 };
591 }
592 }
593 }
594
595 // END REGION NAME
596
597 #[cfg(test)]
598 mod test_message {
599 use super::*;
600
601 #[test]
test_dns_responses() -> Result<()>602 fn test_dns_responses() -> Result<()> {
603 let bytes: [u8; 81] = [
604 0xc2, 0x87, 0x81, 0x80, 0x0, 0x1, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x3, 0x69, 0x62, 0x6d,
605 0x3, 0x63, 0x6f, 0x6d, 0x0, 0x0, 0x1c, 0x0, 0x1, 0xc0, 0xc, 0x0, 0x1c, 0x0, 0x1, 0x0,
606 0x0, 0x0, 0x8, 0x0, 0x10, 0x26, 0x0, 0x14, 0x6, 0x5e, 0x0, 0x2, 0x93, 0x0, 0x0, 0x0,
607 0x0, 0x0, 0x0, 0x38, 0x31, 0xc0, 0xc, 0x0, 0x1c, 0x0, 0x1, 0x0, 0x0, 0x0, 0x8, 0x0,
608 0x10, 0x26, 0x0, 0x14, 0x6, 0x5e, 0x0, 0x2, 0xaa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x38,
609 0x31,
610 ];
611 let bytes: &[u8] = &bytes;
612 let answers = parse_answers(bytes)?;
613 assert_eq!(
614 *answers.get(0).unwrap(),
615 (Ipv6Addr::from_str("2600:1406:5e00:293::3831")?.into(), "ibm.com".to_string())
616 );
617 assert_eq!(
618 *answers.get(1).unwrap(),
619 (Ipv6Addr::from_str("2600:1406:5e00:2aa::3831")?.into(), "ibm.com".to_string())
620 );
621 Ok(())
622 }
623 }
624