xref: /aosp_15_r20/tools/netsim/rust/http-proxy/src/dns.rs (revision cf78ab8cffb8fc9207af348f23af247fb04370a6)
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