/src/suricata7/rust/src/dns/parser.rs
Line | Count | Source |
1 | | /* Copyright (C) 2017 Open Information Security Foundation |
2 | | * |
3 | | * You can copy, redistribute or modify this Program under the terms of |
4 | | * the GNU General Public License version 2 as published by the Free |
5 | | * Software Foundation. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * version 2 along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 | | * 02110-1301, USA. |
16 | | */ |
17 | | |
18 | | //! Nom parsers for DNS. |
19 | | |
20 | | use crate::dns::dns::*; |
21 | | use nom7::combinator::{complete, rest}; |
22 | | use nom7::error::ErrorKind; |
23 | | use nom7::multi::{count, length_data, many_m_n}; |
24 | | use nom7::number::streaming::{be_u16, be_u32, be_u8}; |
25 | | use nom7::{error_position, Err, IResult}; |
26 | | |
27 | | // Parse a DNS header. |
28 | 3.10M | pub fn dns_parse_header(i: &[u8]) -> IResult<&[u8], DNSHeader> { |
29 | 3.10M | let (i, tx_id) = be_u16(i)?; |
30 | 3.09M | let (i, flags) = be_u16(i)?; |
31 | 3.07M | let (i, questions) = be_u16(i)?; |
32 | 3.06M | let (i, answer_rr) = be_u16(i)?; |
33 | 3.06M | let (i, authority_rr) = be_u16(i)?; |
34 | 3.06M | let (i, additional_rr) = be_u16(i)?; |
35 | 3.05M | Ok(( |
36 | 3.05M | i, |
37 | 3.05M | DNSHeader { |
38 | 3.05M | tx_id, |
39 | 3.05M | flags, |
40 | 3.05M | questions, |
41 | 3.05M | answer_rr, |
42 | 3.05M | authority_rr, |
43 | 3.05M | additional_rr, |
44 | 3.05M | }, |
45 | 3.05M | )) |
46 | 3.10M | } |
47 | | |
48 | | // Set a maximum assembled hostname length of 1025, this value was |
49 | | // chosen as its what DNSMasq uses, a popular DNS server, even if most |
50 | | // tooling limits names to 256 chars without special options. |
51 | | static MAX_NAME_LEN: usize = 1025; |
52 | | |
53 | | /// Parse a DNS name. |
54 | | /// |
55 | | /// Names are parsed with the following restrictions: |
56 | | /// |
57 | | /// - Only 255 segments will be processed, if more the parser may |
58 | | /// error out. This is also our safeguard against an infinite loop. If |
59 | | /// a pointer had been followed a truncated name will be |
60 | | /// returned. However if pointer has been processed we error out as we |
61 | | /// don't know where the next data point starts without more |
62 | | /// iterations. |
63 | | /// |
64 | | /// - The maximum name parsed in representation format is MAX_NAME_LEN |
65 | | /// characters. Once larger, the truncated name will be returned with |
66 | | /// a flag specifying the name was truncated. Note that parsing |
67 | | /// continues if no pointer has been used as we still need to find the |
68 | | /// start of the next protocol unit. |
69 | | /// |
70 | | /// As some error in parsing the name are recoverable, a DNSName |
71 | | /// object is returned with flags signifying a recoverable |
72 | | /// error. These errors include: |
73 | | /// |
74 | | /// - infinite loop: as we know the end of the name in the input |
75 | | /// stream, we can return what we've parsed with the remain data. |
76 | | /// |
77 | | /// - maximum number of segments/labels parsed |
78 | | /// |
79 | | /// - truncation of name when too long |
80 | | /// |
81 | | /// Parameters: |
82 | | /// start: the start of the name |
83 | | /// message: the complete message that start is a part of with the DNS header |
84 | 2.69M | pub fn dns_parse_name<'b>(start: &'b [u8], message: &'b [u8], parse_flags: &mut DNSNameFlags) -> IResult<&'b [u8], DNSName> { |
85 | 2.69M | let mut pos = start; |
86 | 2.69M | let mut pivot = start; |
87 | 2.69M | let mut name: Vec<u8> = Vec::with_capacity(32); |
88 | 2.69M | let mut count = 0; |
89 | 2.69M | let mut flags = DNSNameFlags::default(); |
90 | | |
91 | | loop { |
92 | 24.9M | if pos.is_empty() { |
93 | 7.89k | break; |
94 | 24.9M | } |
95 | | |
96 | 24.9M | let len = pos[0]; |
97 | | |
98 | 24.9M | if len == 0x00 { |
99 | 2.39M | pos = &pos[1..]; |
100 | 2.39M | break; |
101 | 22.5M | } else if len & 0b1100_0000 == 0 { |
102 | 6.54M | let (rem, label) = length_data(be_u8)(pos)?; |
103 | 6.54M | if !flags.contains(DNSNameFlags::TRUNCATED) { |
104 | 6.48M | if !name.is_empty() { |
105 | 6.04M | name.push(b'.'); |
106 | 6.04M | } |
107 | 6.48M | name.extend(label); |
108 | 57.6k | } |
109 | 6.54M | pos = rem; |
110 | 16.0M | } else if len & 0b1100_0000 == 0b1100_0000 { |
111 | 16.0M | let (rem, leader) = be_u16(pos)?; |
112 | 16.0M | let offset = usize::from(leader) & 0x3fff; |
113 | 16.0M | if offset > message.len() { |
114 | 5.74k | return Err(Err::Error(error_position!(pos, ErrorKind::OctDigit))); |
115 | 16.0M | } |
116 | | |
117 | 16.0M | if &message[offset..] == pos { |
118 | | // Self reference, immedate infinite loop. |
119 | 135k | flags.insert(DNSNameFlags::INFINITE_LOOP); |
120 | | |
121 | | // If we have followed a pointer, we can just break as |
122 | | // we've already found the end of the input. But if we |
123 | | // have not followed a pointer yet return a parse |
124 | | // error. |
125 | 135k | if pivot != start { |
126 | 134k | break; |
127 | 254 | } |
128 | 254 | return Err(Err::Error(error_position!(pos, ErrorKind::OctDigit))); |
129 | 15.8M | } |
130 | | |
131 | 15.8M | pos = &message[offset..]; |
132 | 15.8M | if pivot == start { |
133 | 447k | pivot = rem; |
134 | 15.4M | } |
135 | | } else { |
136 | 13.3k | return Err(Err::Error(error_position!(pos, ErrorKind::OctDigit))); |
137 | | } |
138 | | |
139 | | // Return error if we've looped a certain number of times. |
140 | 22.4M | count += 1; |
141 | | |
142 | 22.4M | if count > 255 { |
143 | 70.7k | flags.insert(DNSNameFlags::LABEL_LIMIT); |
144 | | |
145 | | // Our segment limit has been reached, if we have hit a |
146 | | // pointer we can just return the truncated name. If we |
147 | | // have not hit a pointer, we need to bail with an error. |
148 | 70.7k | if pivot != start { |
149 | 70.4k | flags.insert(DNSNameFlags::TRUNCATED); |
150 | 70.4k | break; |
151 | 280 | } |
152 | 280 | return Err(Err::Error(error_position!(pos, ErrorKind::OctDigit))); |
153 | 22.3M | } |
154 | | |
155 | 22.3M | if name.len() > MAX_NAME_LEN { |
156 | 59.9k | name.truncate(MAX_NAME_LEN); |
157 | 59.9k | flags.insert(DNSNameFlags::TRUNCATED); |
158 | | |
159 | | // If we have pivoted due to a pointer we know where the |
160 | | // end of the data is, so we can break early. Otherwise |
161 | | // we'll keep parsing in hopes to find the end of the name |
162 | | // so parsing can continue. |
163 | 59.9k | if pivot != start { |
164 | 59.0k | break; |
165 | 827 | } |
166 | 22.2M | } |
167 | | } |
168 | | |
169 | 2.66M | parse_flags.insert(flags); |
170 | | |
171 | | // If we followed a pointer we return the position after the first |
172 | | // pointer followed. Is there a better way to see if these slices |
173 | | // diverged from each other? A straight up comparison would |
174 | | // actually check the contents. |
175 | 2.66M | if pivot != start { |
176 | 446k | Ok((pivot, DNSName { value: name, flags })) |
177 | | } else { |
178 | 2.22M | Ok((pos, DNSName { value: name, flags })) |
179 | | } |
180 | 2.69M | } |
181 | | |
182 | | /// Parse answer entries. |
183 | | /// |
184 | | /// In keeping with the C implementation, answer values that can |
185 | | /// contain multiple answers get expanded into their own answer |
186 | | /// records. An example of this is a TXT record with multiple strings |
187 | | /// in it - each string will be expanded to its own answer record. |
188 | | /// |
189 | | /// This function could be a made a whole lot simpler if we logged a |
190 | | /// multi-string TXT entry as a single quote string, similar to the |
191 | | /// output of dig. Something to consider for a future version. |
192 | 3.13M | fn dns_parse_answer<'a>( |
193 | 3.13M | slice: &'a [u8], message: &'a [u8], count: usize, flags: &mut DNSNameFlags, |
194 | 3.13M | ) -> IResult<&'a [u8], Vec<DNSAnswerEntry>> { |
195 | 3.13M | let mut answers = Vec::new(); |
196 | 3.13M | let mut input = slice; |
197 | | |
198 | | struct Answer<'a> { |
199 | | name: DNSName, |
200 | | rrtype: u16, |
201 | | rrclass: u16, |
202 | | ttl: u32, |
203 | | data: &'a [u8], |
204 | | } |
205 | | |
206 | 185k | fn subparser<'a>( |
207 | 185k | i: &'a [u8], message: &'a [u8], flags: &mut DNSNameFlags, |
208 | 185k | ) -> IResult<&'a [u8], Answer<'a>> { |
209 | 185k | let (i, name) = dns_parse_name(i, message, flags)?; |
210 | 181k | let (i, rrtype) = be_u16(i)?; |
211 | 179k | let (i, rrclass) = be_u16(i)?; |
212 | 178k | let (i, ttl) = be_u32(i)?; |
213 | 176k | let (i, data) = length_data(be_u16)(i)?; |
214 | 168k | let answer = Answer { |
215 | 168k | name, |
216 | 168k | rrtype, |
217 | 168k | rrclass, |
218 | 168k | ttl, |
219 | 168k | data, |
220 | 168k | }; |
221 | 168k | Ok((i, answer)) |
222 | 185k | } |
223 | | |
224 | 3.13M | for _ in 0..count { |
225 | 185k | match subparser(input, message, flags) { |
226 | 168k | Ok((rem, val)) => { |
227 | 168k | let n = match val.rrtype { |
228 | | DNS_RECORD_TYPE_TXT => { |
229 | | // For TXT records we need to run the parser |
230 | | // multiple times. Set n high, to the maximum |
231 | | // value based on a max txt side of 65535, but |
232 | | // taking into considering that strings need |
233 | | // to be quoted, so half that. |
234 | 4.11k | 32767 |
235 | | } |
236 | | _ => { |
237 | | // For all other types we only want to run the |
238 | | // parser once, so set n to 1. |
239 | 164k | 1 |
240 | | } |
241 | | }; |
242 | 168k | let result: IResult<&'a [u8], Vec<DNSRData>> = if val.data.is_empty() { |
243 | | // many_m_n returns ErrorKind:ManyMN on empty data as it does not consume |
244 | 46.1k | Ok((val.data, Vec::new())) |
245 | | } else { |
246 | 1.26M | many_m_n(1, n, complete(|b| dns_parse_rdata(b, message, val.rrtype, flags)))(val.data) |
247 | | }; |
248 | 168k | match result { |
249 | 155k | Ok((_, rdatas)) => { |
250 | 1.40M | for rdata in rdatas { |
251 | 1.24M | answers.push(DNSAnswerEntry { |
252 | 1.24M | name: val.name.clone(), |
253 | 1.24M | rrtype: val.rrtype, |
254 | 1.24M | rrclass: val.rrclass, |
255 | 1.24M | ttl: val.ttl, |
256 | 1.24M | data: rdata, |
257 | 1.24M | }); |
258 | 1.24M | } |
259 | | } |
260 | 13.1k | Err(e) => { |
261 | 13.1k | return Err(e); |
262 | | } |
263 | | } |
264 | 155k | input = rem; |
265 | | } |
266 | 17.2k | Err(e) => { |
267 | 17.2k | return Err(e); |
268 | | } |
269 | | } |
270 | | } |
271 | | |
272 | 3.10M | return Ok((input, answers)); |
273 | 3.13M | } |
274 | | |
275 | 1.58M | pub fn dns_parse_response_body<'a>( |
276 | 1.58M | i: &'a [u8], message: &'a [u8], header: DNSHeader, |
277 | 1.58M | ) -> IResult<&'a [u8], (DNSResponse, DNSNameFlags)> { |
278 | 1.58M | let mut flags = DNSNameFlags::default(); |
279 | 1.58M | let (i, queries) = count(|b| dns_parse_query(b, message, &mut flags), header.questions as usize)(i)?; |
280 | 1.58M | let (i, answers) = dns_parse_answer(i, message, header.answer_rr as usize, &mut flags)?; |
281 | 1.55M | let (i, authorities) = dns_parse_answer(i, message, header.authority_rr as usize, &mut flags)?; |
282 | 1.55M | Ok(( |
283 | 1.55M | i, |
284 | 1.55M | (DNSResponse { |
285 | 1.55M | header, |
286 | 1.55M | queries, |
287 | 1.55M | answers, |
288 | 1.55M | authorities, |
289 | 1.55M | }, flags), |
290 | 1.55M | )) |
291 | 1.58M | } |
292 | | |
293 | | /// Parse a single DNS query. |
294 | | /// |
295 | | /// Arguments are suitable for using with call!: |
296 | | /// |
297 | | /// call!(complete_dns_message_buffer) |
298 | 2.41M | pub fn dns_parse_query<'a>(input: &'a [u8], message: &'a [u8], flags: &mut DNSNameFlags) -> IResult<&'a [u8], DNSQueryEntry> { |
299 | 2.41M | let i = input; |
300 | 2.41M | let (i, name) = dns_parse_name(i, message, flags)?; |
301 | 2.39M | let (i, rrtype) = be_u16(i)?; |
302 | 2.39M | let (i, rrclass) = be_u16(i)?; |
303 | 2.38M | Ok(( |
304 | 2.38M | i, |
305 | 2.38M | DNSQueryEntry { |
306 | 2.38M | name, |
307 | 2.38M | rrtype, |
308 | 2.38M | rrclass, |
309 | 2.38M | }, |
310 | 2.38M | )) |
311 | 2.41M | } |
312 | | |
313 | 15.6k | fn dns_parse_rdata_a(input: &[u8]) -> IResult<&[u8], DNSRData> { |
314 | 15.6k | rest(input).map(|(input, data)| (input, DNSRData::A(data.to_vec()))) |
315 | 15.6k | } |
316 | | |
317 | 7.99k | fn dns_parse_rdata_aaaa(input: &[u8]) -> IResult<&[u8], DNSRData> { |
318 | 7.99k | rest(input).map(|(input, data)| (input, DNSRData::AAAA(data.to_vec()))) |
319 | 7.99k | } |
320 | | |
321 | 5.80k | fn dns_parse_rdata_cname<'a>( |
322 | 5.80k | input: &'a [u8], message: &'a [u8], flags: &mut DNSNameFlags, |
323 | 5.80k | ) -> IResult<&'a [u8], DNSRData> { |
324 | 5.80k | dns_parse_name(input, message, flags).map(|(input, name)| (input, DNSRData::CNAME(name))) |
325 | 5.80k | } |
326 | | |
327 | 54.3k | fn dns_parse_rdata_ns<'a>( |
328 | 54.3k | input: &'a [u8], message: &'a [u8], flags: &mut DNSNameFlags, |
329 | 54.3k | ) -> IResult<&'a [u8], DNSRData> { |
330 | 54.3k | dns_parse_name(input, message, flags).map(|(input, name)| (input, DNSRData::NS(name))) |
331 | 54.3k | } |
332 | | |
333 | 3.58k | fn dns_parse_rdata_ptr<'a>( |
334 | 3.58k | input: &'a [u8], message: &'a [u8], flags: &mut DNSNameFlags, |
335 | 3.58k | ) -> IResult<&'a [u8], DNSRData> { |
336 | 3.58k | dns_parse_name(input, message, flags).map(|(input, name)| (input, DNSRData::PTR(name))) |
337 | 3.58k | } |
338 | | |
339 | 14.7k | fn dns_parse_rdata_soa<'a>( |
340 | 14.7k | input: &'a [u8], message: &'a [u8], flags: &mut DNSNameFlags, |
341 | 14.7k | ) -> IResult<&'a [u8], DNSRData> { |
342 | 14.7k | let i = input; |
343 | 14.7k | let (i, mname) = dns_parse_name(i, message, flags)?; |
344 | 14.2k | let (i, rname) = dns_parse_name(i, message, flags)?; |
345 | 13.2k | let (i, serial) = be_u32(i)?; |
346 | 12.3k | let (i, refresh) = be_u32(i)?; |
347 | 10.2k | let (i, retry) = be_u32(i)?; |
348 | 9.29k | let (i, expire) = be_u32(i)?; |
349 | 8.25k | let (i, minimum) = be_u32(i)?; |
350 | 6.34k | Ok(( |
351 | 6.34k | i, |
352 | 6.34k | DNSRData::SOA(DNSRDataSOA { |
353 | 6.34k | mname, |
354 | 6.34k | rname, |
355 | 6.34k | serial, |
356 | 6.34k | refresh, |
357 | 6.34k | retry, |
358 | 6.34k | expire, |
359 | 6.34k | minimum, |
360 | 6.34k | }), |
361 | 6.34k | )) |
362 | 14.7k | } |
363 | | |
364 | 1.05k | fn dns_parse_rdata_mx<'a>( |
365 | 1.05k | input: &'a [u8], message: &'a [u8], flags: &mut DNSNameFlags, |
366 | 1.05k | ) -> IResult<&'a [u8], DNSRData> { |
367 | | // For MX we skip over the preference field before |
368 | | // parsing out the name. |
369 | 1.05k | let (i, _) = be_u16(input)?; |
370 | 623 | let (i, name) = dns_parse_name(i, message, flags)?; |
371 | 395 | Ok((i, DNSRData::MX(name))) |
372 | 1.05k | } |
373 | | |
374 | 2.69k | fn dns_parse_rdata_srv<'a>( |
375 | 2.69k | input: &'a [u8], message: &'a [u8], flags: &mut DNSNameFlags, |
376 | 2.69k | ) -> IResult<&'a [u8], DNSRData> { |
377 | 2.69k | let i = input; |
378 | 2.69k | let (i, priority) = be_u16(i)?; |
379 | 2.46k | let (i, weight) = be_u16(i)?; |
380 | 1.95k | let (i, port) = be_u16(i)?; |
381 | 1.67k | let (i, target) = dns_parse_name(i, message, flags)?; |
382 | 1.18k | Ok(( |
383 | 1.18k | i, |
384 | 1.18k | DNSRData::SRV(DNSRDataSRV { |
385 | 1.18k | priority, |
386 | 1.18k | weight, |
387 | 1.18k | port, |
388 | 1.18k | target, |
389 | 1.18k | }), |
390 | 1.18k | )) |
391 | 2.69k | } |
392 | | |
393 | 1.14M | fn dns_parse_rdata_txt(input: &[u8]) -> IResult<&[u8], DNSRData> { |
394 | 1.14M | let (i, txt) = length_data(be_u8)(input)?; |
395 | 1.14M | Ok((i, DNSRData::TXT(txt.to_vec()))) |
396 | 1.14M | } |
397 | | |
398 | 525 | fn dns_parse_rdata_null(input: &[u8]) -> IResult<&[u8], DNSRData> { |
399 | 525 | rest(input).map(|(input, data)| (input, DNSRData::NULL(data.to_vec()))) |
400 | 525 | } |
401 | | |
402 | 2.63k | fn dns_parse_rdata_sshfp(input: &[u8]) -> IResult<&[u8], DNSRData> { |
403 | 2.63k | let i = input; |
404 | 2.63k | let (i, algo) = be_u8(i)?; |
405 | 2.63k | let (i, fp_type) = be_u8(i)?; |
406 | 2.16k | let fingerprint = i; |
407 | 2.16k | Ok(( |
408 | 2.16k | &[], |
409 | 2.16k | DNSRData::SSHFP(DNSRDataSSHFP { |
410 | 2.16k | algo, |
411 | 2.16k | fp_type, |
412 | 2.16k | fingerprint: fingerprint.to_vec(), |
413 | 2.16k | }), |
414 | 2.16k | )) |
415 | 2.63k | } |
416 | | |
417 | 8.86k | fn dns_parse_rdata_unknown(input: &[u8]) -> IResult<&[u8], DNSRData> { |
418 | 8.86k | rest(input).map(|(input, data)| (input, DNSRData::Unknown(data.to_vec()))) |
419 | 8.86k | } |
420 | | |
421 | 1.26M | pub fn dns_parse_rdata<'a>( |
422 | 1.26M | input: &'a [u8], message: &'a [u8], rrtype: u16, flags: &mut DNSNameFlags |
423 | 1.26M | ) -> IResult<&'a [u8], DNSRData> { |
424 | 1.26M | match rrtype { |
425 | 15.6k | DNS_RECORD_TYPE_A => dns_parse_rdata_a(input), |
426 | 7.99k | DNS_RECORD_TYPE_AAAA => dns_parse_rdata_aaaa(input), |
427 | 5.80k | DNS_RECORD_TYPE_CNAME => dns_parse_rdata_cname(input, message, flags), |
428 | 3.58k | DNS_RECORD_TYPE_PTR => dns_parse_rdata_ptr(input, message, flags), |
429 | 14.7k | DNS_RECORD_TYPE_SOA => dns_parse_rdata_soa(input, message, flags), |
430 | 1.05k | DNS_RECORD_TYPE_MX => dns_parse_rdata_mx(input, message, flags), |
431 | 54.3k | DNS_RECORD_TYPE_NS => dns_parse_rdata_ns(input, message, flags), |
432 | 1.14M | DNS_RECORD_TYPE_TXT => dns_parse_rdata_txt(input), |
433 | 525 | DNS_RECORD_TYPE_NULL => dns_parse_rdata_null(input), |
434 | 2.63k | DNS_RECORD_TYPE_SSHFP => dns_parse_rdata_sshfp(input), |
435 | 2.69k | DNS_RECORD_TYPE_SRV => dns_parse_rdata_srv(input, message, flags), |
436 | 8.86k | _ => dns_parse_rdata_unknown(input), |
437 | | } |
438 | 1.26M | } |
439 | | |
440 | | /// Parse a DNS request. |
441 | 23.5k | pub fn dns_parse_request(input: &[u8]) -> IResult<&[u8], (DNSRequest, DNSNameFlags)> { |
442 | 23.5k | let i = input; |
443 | 23.5k | let (i, header) = dns_parse_header(i)?; |
444 | 22.8k | dns_parse_request_body(i, input, header) |
445 | 23.5k | } |
446 | | |
447 | 1.39M | pub fn dns_parse_request_body<'a>( |
448 | 1.39M | input: &'a [u8], message: &'a [u8], header: DNSHeader, |
449 | 1.39M | ) -> IResult<&'a [u8], (DNSRequest, DNSNameFlags)> { |
450 | 1.39M | let mut flags = DNSNameFlags::default(); |
451 | 1.39M | let i = input; |
452 | 1.43M | let (i, queries) = count(|b| dns_parse_query(b, message, &mut flags), header.questions as usize)(i)?; |
453 | 1.37M | Ok((i, (DNSRequest { header, queries }, flags))) |
454 | 1.39M | } |
455 | | |
456 | | #[cfg(test)] |
457 | | mod tests { |
458 | | |
459 | | use crate::dns::dns::{DNSAnswerEntry, DNSHeader}; |
460 | | use crate::dns::parser::*; |
461 | | |
462 | | /// Parse a simple name with no pointers. |
463 | | #[test] |
464 | | fn test_dns_parse_name() { |
465 | | let buf: &[u8] = &[ |
466 | | 0x09, 0x63, /* .......c */ |
467 | | 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2d, 0x63, 0x66, /* lient-cf */ |
468 | | 0x07, 0x64, 0x72, 0x6f, 0x70, 0x62, 0x6f, 0x78, /* .dropbox */ |
469 | | 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, /* .com.... */ |
470 | | ]; |
471 | | let expected_remainder: &[u8] = &[0x00, 0x01, 0x00]; |
472 | | let mut flags = DNSNameFlags::default(); |
473 | | let (remainder, name) = dns_parse_name(buf, buf, &mut flags).unwrap(); |
474 | | assert_eq!("client-cf.dropbox.com".as_bytes(), &name.value[..]); |
475 | | assert_eq!(remainder, expected_remainder); |
476 | | } |
477 | | |
478 | | /// Test parsing a name with pointers. |
479 | | #[test] |
480 | | fn test_dns_parse_name_with_pointer() { |
481 | | let buf: &[u8] = &[ |
482 | | 0xd8, 0xcb, 0x8a, 0xed, 0xa1, 0x46, 0x00, 0x15, /* 0 - .....F.. */ |
483 | | 0x17, 0x0d, 0x06, 0xf7, 0x08, 0x00, 0x45, 0x00, /* 8 - ......E. */ |
484 | | 0x00, 0x7b, 0x71, 0x6e, 0x00, 0x00, 0x39, 0x11, /* 16 - .{qn..9. */ |
485 | | 0xf4, 0xd9, 0x08, 0x08, 0x08, 0x08, 0x0a, 0x10, /* 24 - ........ */ |
486 | | 0x01, 0x0b, 0x00, 0x35, 0xe1, 0x8e, 0x00, 0x67, /* 32 - ...5...g */ |
487 | | 0x60, 0x00, 0xef, 0x08, 0x81, 0x80, 0x00, 0x01, /* 40 - `....... */ |
488 | | 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x03, 0x77, /* 48 - .......w */ |
489 | | 0x77, 0x77, 0x0c, 0x73, 0x75, 0x72, 0x69, 0x63, /* 56 - ww.suric */ |
490 | | 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, 0x73, 0x03, /* 64 - ata-ids. */ |
491 | | 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01, /* 72 - org..... */ |
492 | | 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, /* 80 - ........ */ |
493 | | 0x0e, 0x0f, 0x00, 0x02, 0xc0, 0x10, 0xc0, 0x10, /* 88 - ........ */ |
494 | | 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x2b, /* 96 - .......+ */ |
495 | | 0x00, 0x04, 0xc0, 0x00, 0x4e, 0x19, 0xc0, 0x10, /* 104 - ....N... */ |
496 | | 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x2b, /* 112 - .......+ */ |
497 | | 0x00, 0x04, 0xc0, 0x00, 0x4e, 0x18, 0x00, 0x00, /* 120 - ....N... */ |
498 | | 0x29, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 128 - )....... */ |
499 | | 0x00, /* 136 - . */ |
500 | | ]; |
501 | | |
502 | | // The DNS payload starts at offset 42. |
503 | | let message = &buf[42..]; |
504 | | |
505 | | // The name at offset 54 is the complete name. |
506 | | let start1 = &buf[54..]; |
507 | | let mut flags = DNSNameFlags::default(); |
508 | | let res1 = dns_parse_name(start1, message, &mut flags); |
509 | | assert_eq!( |
510 | | res1, |
511 | | Ok(( |
512 | | &start1[22..], |
513 | | DNSName { |
514 | | value: "www.suricata-ids.org".as_bytes().to_vec(), |
515 | | flags: DNSNameFlags::default(), |
516 | | } |
517 | | )) |
518 | | ); |
519 | | |
520 | | // The second name starts at offset 80, but is just a pointer |
521 | | // to the first. |
522 | | let start2 = &buf[80..]; |
523 | | let mut flags = DNSNameFlags::default(); |
524 | | let res2 = dns_parse_name(start2, message, &mut flags); |
525 | | assert_eq!( |
526 | | res2, |
527 | | Ok(( |
528 | | &start2[2..], |
529 | | DNSName { |
530 | | value: "www.suricata-ids.org".as_bytes().to_vec(), |
531 | | flags: DNSNameFlags::default() |
532 | | } |
533 | | )) |
534 | | ); |
535 | | |
536 | | // The third name starts at offset 94, but is a pointer to a |
537 | | // portion of the first. |
538 | | let start3 = &buf[94..]; |
539 | | let mut flags = DNSNameFlags::default(); |
540 | | let res3 = dns_parse_name(start3, message, &mut flags); |
541 | | assert_eq!( |
542 | | res3, |
543 | | Ok(( |
544 | | &start3[2..], |
545 | | DNSName { |
546 | | value: "suricata-ids.org".as_bytes().to_vec(), |
547 | | flags: DNSNameFlags::default() |
548 | | } |
549 | | )) |
550 | | ); |
551 | | |
552 | | // The fourth name starts at offset 110, but is a pointer to a |
553 | | // portion of the first. |
554 | | let start4 = &buf[110..]; |
555 | | let mut flags = DNSNameFlags::default(); |
556 | | let res4 = dns_parse_name(start4, message, &mut flags); |
557 | | assert_eq!( |
558 | | res4, |
559 | | Ok(( |
560 | | &start4[2..], |
561 | | DNSName { |
562 | | value: "suricata-ids.org".as_bytes().to_vec(), |
563 | | flags: DNSNameFlags::default() |
564 | | } |
565 | | )) |
566 | | ); |
567 | | } |
568 | | |
569 | | #[test] |
570 | | fn test_dns_parse_name_double_pointer() { |
571 | | let buf: &[u8] = &[ |
572 | | 0xd8, 0xcb, 0x8a, 0xed, 0xa1, 0x46, 0x00, 0x15, /* 0: .....F.. */ |
573 | | 0x17, 0x0d, 0x06, 0xf7, 0x08, 0x00, 0x45, 0x00, /* 8: ......E. */ |
574 | | 0x00, 0x66, 0x5e, 0x20, 0x40, 0x00, 0x40, 0x11, /* 16: .f^ @.@. */ |
575 | | 0xc6, 0x3b, 0x0a, 0x10, 0x01, 0x01, 0x0a, 0x10, /* 24: .;...... */ |
576 | | 0x01, 0x0b, 0x00, 0x35, 0xc2, 0x21, 0x00, 0x52, /* 32: ...5.!.R */ |
577 | | 0x35, 0xc5, 0x0d, 0x4f, 0x81, 0x80, 0x00, 0x01, /* 40: 5..O.... */ |
578 | | 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x05, 0x62, /* 48: .......b */ |
579 | | 0x6c, 0x6f, 0x63, 0x6b, 0x07, 0x64, 0x72, 0x6f, /* 56: lock.dro */ |
580 | | 0x70, 0x62, 0x6f, 0x78, 0x03, 0x63, 0x6f, 0x6d, /* 64: pbox.com */ |
581 | | 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, /* 72: ........ */ |
582 | | 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, /* 80: ........ */ |
583 | | 0x0b, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x02, /* 88: ..block. */ |
584 | | 0x67, 0x31, 0xc0, 0x12, 0xc0, 0x2f, 0x00, 0x01, /* 96: g1.../.. */ |
585 | | 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, /* 104: ........ */ |
586 | | 0x2d, 0x3a, 0x46, 0x21, /* 112: -:F! */ |
587 | | ]; |
588 | | |
589 | | // The start of the DNS message in the above packet. |
590 | | let message: &[u8] = &buf[42..]; |
591 | | |
592 | | // The start of the name we want to parse, 0xc0 0x2f, a |
593 | | // pointer to offset 47 in the message (or 89 in the full |
594 | | // packet). |
595 | | let start: &[u8] = &buf[100..]; |
596 | | |
597 | | let mut flags = DNSNameFlags::default(); |
598 | | let res = dns_parse_name(start, message, &mut flags); |
599 | | assert_eq!( |
600 | | res, |
601 | | Ok(( |
602 | | &start[2..], |
603 | | DNSName { |
604 | | value: "block.g1.dropbox.com".as_bytes().to_vec(), |
605 | | flags: DNSNameFlags::default() |
606 | | } |
607 | | )) |
608 | | ); |
609 | | } |
610 | | |
611 | | #[test] |
612 | | fn test_dns_parse_request() { |
613 | | // DNS request from dig-a-www.suricata-ids.org.pcap. |
614 | | let pkt: &[u8] = &[ |
615 | | 0x8d, 0x32, 0x01, 0x20, 0x00, 0x01, /* ...2. .. */ |
616 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x77, /* .......w */ |
617 | | 0x77, 0x77, 0x0c, 0x73, 0x75, 0x72, 0x69, 0x63, /* ww.suric */ |
618 | | 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, 0x73, 0x03, /* ata-ids. */ |
619 | | 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01, /* org..... */ |
620 | | 0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00, 0x00, /* ..)..... */ |
621 | | 0x00, 0x00, 0x00, /* ... */ |
622 | | ]; |
623 | | |
624 | | let (rem, (request, _flags)) = dns_parse_request(pkt).unwrap(); |
625 | | // For now we have some remainder data as there is an |
626 | | // additional record type we don't parse yet. |
627 | | assert!(!rem.is_empty()); |
628 | | |
629 | | assert_eq!( |
630 | | request.header, |
631 | | DNSHeader { |
632 | | tx_id: 0x8d32, |
633 | | flags: 0x0120, |
634 | | questions: 1, |
635 | | answer_rr: 0, |
636 | | authority_rr: 0, |
637 | | additional_rr: 1, |
638 | | } |
639 | | ); |
640 | | |
641 | | assert_eq!(request.queries.len(), 1); |
642 | | let query = &request.queries[0]; |
643 | | assert_eq!(query.name.value, "www.suricata-ids.org".as_bytes().to_vec()); |
644 | | assert_eq!(query.rrtype, 1); |
645 | | assert_eq!(query.rrclass, 1); |
646 | | } |
647 | | |
648 | | /// Parse a DNS response. |
649 | | fn dns_parse_response(message: &[u8]) -> IResult<&[u8], (DNSResponse, DNSNameFlags)> { |
650 | | let i = message; |
651 | | let (i, header) = dns_parse_header(i)?; |
652 | | dns_parse_response_body(i, message, header) |
653 | | } |
654 | | |
655 | | #[test] |
656 | | fn test_dns_parse_response() { |
657 | | // DNS response from dig-a-www.suricata-ids.org.pcap. |
658 | | let pkt: &[u8] = &[ |
659 | | 0x8d, 0x32, 0x81, 0xa0, 0x00, 0x01, /* ...2.... */ |
660 | | 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x77, /* .......w */ |
661 | | 0x77, 0x77, 0x0c, 0x73, 0x75, 0x72, 0x69, 0x63, /* ww.suric */ |
662 | | 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, 0x73, 0x03, /* ata-ids. */ |
663 | | 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01, /* org..... */ |
664 | | 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, /* ........ */ |
665 | | 0x0d, 0xd8, 0x00, 0x12, 0x0c, 0x73, 0x75, 0x72, /* .....sur */ |
666 | | 0x69, 0x63, 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, /* icata-id */ |
667 | | 0x73, 0x03, 0x6f, 0x72, 0x67, 0x00, 0xc0, 0x32, /* s.org..2 */ |
668 | | 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf4, /* ........ */ |
669 | | 0x00, 0x04, 0xc0, 0x00, 0x4e, 0x18, 0xc0, 0x32, /* ....N..2 */ |
670 | | 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf4, /* ........ */ |
671 | | 0x00, 0x04, 0xc0, 0x00, 0x4e, 0x19, /* ....N. */ |
672 | | ]; |
673 | | |
674 | | let (rem, (response, _flags)) = dns_parse_response(pkt).unwrap(); |
675 | | // The response should be full parsed. |
676 | | assert_eq!(rem.len(), 0); |
677 | | |
678 | | assert_eq!( |
679 | | response.header, |
680 | | DNSHeader { |
681 | | tx_id: 0x8d32, |
682 | | flags: 0x81a0, |
683 | | questions: 1, |
684 | | answer_rr: 3, |
685 | | authority_rr: 0, |
686 | | additional_rr: 0, |
687 | | } |
688 | | ); |
689 | | |
690 | | assert_eq!(response.answers.len(), 3); |
691 | | |
692 | | let answer1 = &response.answers[0]; |
693 | | assert_eq!(answer1.name.value, "www.suricata-ids.org".as_bytes().to_vec()); |
694 | | assert_eq!(answer1.rrtype, 5); |
695 | | assert_eq!(answer1.rrclass, 1); |
696 | | assert_eq!(answer1.ttl, 3544); |
697 | | assert_eq!( |
698 | | answer1.data, |
699 | | DNSRData::CNAME(DNSName { |
700 | | value: "suricata-ids.org".as_bytes().to_vec(), |
701 | | flags: Default::default(), |
702 | | }) |
703 | | ); |
704 | | |
705 | | let answer2 = &response.answers[1]; |
706 | | assert_eq!( |
707 | | answer2, |
708 | | &DNSAnswerEntry { |
709 | | name: DNSName { |
710 | | value: "suricata-ids.org".as_bytes().to_vec(), |
711 | | flags: Default::default(), |
712 | | }, |
713 | | rrtype: 1, |
714 | | rrclass: 1, |
715 | | ttl: 244, |
716 | | data: DNSRData::A([192, 0, 78, 24].to_vec()), |
717 | | } |
718 | | ); |
719 | | |
720 | | let answer3 = &response.answers[2]; |
721 | | assert_eq!( |
722 | | answer3, |
723 | | &DNSAnswerEntry { |
724 | | name: DNSName { |
725 | | value: "suricata-ids.org".as_bytes().to_vec(), |
726 | | flags: Default::default(), |
727 | | }, |
728 | | rrtype: 1, |
729 | | rrclass: 1, |
730 | | ttl: 244, |
731 | | data: DNSRData::A([192, 0, 78, 25].to_vec()), |
732 | | } |
733 | | ) |
734 | | } |
735 | | |
736 | | #[test] |
737 | | fn test_dns_parse_response_nxdomain_soa() { |
738 | | // DNS response with an SOA authority record from |
739 | | // dns-udp-nxdomain-soa.pcap. |
740 | | let pkt: &[u8] = &[ |
741 | | 0x82, 0x95, 0x81, 0x83, 0x00, 0x01, /* j....... */ |
742 | | 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x03, 0x64, /* .......d */ |
743 | | 0x6e, 0x65, 0x04, 0x6f, 0x69, 0x73, 0x66, 0x03, /* ne.oisf. */ |
744 | | 0x6e, 0x65, 0x74, 0x00, 0x00, 0x01, 0x00, 0x01, /* net..... */ |
745 | | 0xc0, 0x10, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, /* ........ */ |
746 | | 0x03, 0x83, 0x00, 0x45, 0x06, 0x6e, 0x73, 0x2d, /* ...E.ns- */ |
747 | | 0x31, 0x31, 0x30, 0x09, 0x61, 0x77, 0x73, 0x64, /* 110.awsd */ |
748 | | 0x6e, 0x73, 0x2d, 0x31, 0x33, 0x03, 0x63, 0x6f, /* ns-13.co */ |
749 | | 0x6d, 0x00, 0x11, 0x61, 0x77, 0x73, 0x64, 0x6e, /* m..awsdn */ |
750 | | 0x73, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x6d, 0x61, /* s-hostma */ |
751 | | 0x73, 0x74, 0x65, 0x72, 0x06, 0x61, 0x6d, 0x61, /* ster.ama */ |
752 | | 0x7a, 0x6f, 0x6e, 0xc0, 0x3b, 0x00, 0x00, 0x00, /* zon.;... */ |
753 | | 0x01, 0x00, 0x00, 0x1c, 0x20, 0x00, 0x00, 0x03, /* .... ... */ |
754 | | 0x84, 0x00, 0x12, 0x75, 0x00, 0x00, 0x01, 0x51, /* ...u...Q */ |
755 | | 0x80, 0x00, 0x00, 0x29, 0x02, 0x00, 0x00, 0x00, /* ...).... */ |
756 | | 0x00, 0x00, 0x00, 0x00, /* .... */ |
757 | | ]; |
758 | | |
759 | | let (rem, (response, _flags)) = dns_parse_response(pkt).unwrap(); |
760 | | // For now we have some remainder data as there is an |
761 | | // additional record type we don't parse yet. |
762 | | assert!(!rem.is_empty()); |
763 | | |
764 | | assert_eq!( |
765 | | response.header, |
766 | | DNSHeader { |
767 | | tx_id: 0x8295, |
768 | | flags: 0x8183, |
769 | | questions: 1, |
770 | | answer_rr: 0, |
771 | | authority_rr: 1, |
772 | | additional_rr: 1, |
773 | | } |
774 | | ); |
775 | | |
776 | | assert_eq!(response.authorities.len(), 1); |
777 | | |
778 | | let authority = &response.authorities[0]; |
779 | | assert_eq!(authority.name.value, "oisf.net".as_bytes().to_vec()); |
780 | | assert_eq!(authority.rrtype, 6); |
781 | | assert_eq!(authority.rrclass, 1); |
782 | | assert_eq!(authority.ttl, 899); |
783 | | assert_eq!( |
784 | | authority.data, |
785 | | DNSRData::SOA(DNSRDataSOA { |
786 | | mname: DNSName { |
787 | | value: "ns-110.awsdns-13.com".as_bytes().to_vec(), |
788 | | flags: DNSNameFlags::default() |
789 | | }, |
790 | | rname: DNSName { |
791 | | value: "awsdns-hostmaster.amazon.com".as_bytes().to_vec(), |
792 | | flags: DNSNameFlags::default() |
793 | | }, |
794 | | serial: 1, |
795 | | refresh: 7200, |
796 | | retry: 900, |
797 | | expire: 1209600, |
798 | | minimum: 86400, |
799 | | }) |
800 | | ); |
801 | | } |
802 | | |
803 | | #[test] |
804 | | fn test_dns_parse_response_null() { |
805 | | // DNS response with a NULL record from |
806 | | // https://redmine.openinfosecfoundation.org/attachments/2062 |
807 | | |
808 | | let pkt: &[u8] = &[ |
809 | | 0x12, 0xb0, 0x84, 0x00, 0x00, 0x01, 0x00, 0x01, /* ........ */ |
810 | | 0x00, 0x00, 0x00, 0x00, 0x0b, 0x76, 0x61, 0x61, /* .....vaa */ |
811 | | 0x61, 0x61, 0x6b, 0x61, 0x72, 0x64, 0x6c, 0x69, /* aakardli */ |
812 | | 0x06, 0x70, 0x69, 0x72, 0x61, 0x74, 0x65, 0x03, /* .pirate. */ |
813 | | 0x73, 0x65, 0x61, 0x00, 0x00, 0x0a, 0x00, 0x01, /* sea..... */ |
814 | | 0xc0, 0x0c, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x00, /* ........ */ |
815 | | 0x00, 0x00, 0x00, 0x09, 0x56, 0x41, 0x43, 0x4b, /* ....VACK */ |
816 | | 0x44, 0x03, 0xc5, 0xe9, 0x01, /* D.... */ |
817 | | ]; |
818 | | |
819 | | let (rem, (response, _flags)) = dns_parse_response(pkt).unwrap(); |
820 | | // The response should be fully parsed. |
821 | | assert_eq!(rem.len(), 0); |
822 | | |
823 | | assert_eq!( |
824 | | response.header, |
825 | | DNSHeader { |
826 | | tx_id: 0x12b0, |
827 | | flags: 0x8400, |
828 | | questions: 1, |
829 | | answer_rr: 1, |
830 | | authority_rr: 0, |
831 | | additional_rr: 0, |
832 | | } |
833 | | ); |
834 | | |
835 | | assert_eq!(response.queries.len(), 1); |
836 | | let query = &response.queries[0]; |
837 | | assert_eq!(query.name.value, "vaaaakardli.pirate.sea".as_bytes().to_vec()); |
838 | | assert_eq!(query.rrtype, DNS_RECORD_TYPE_NULL); |
839 | | assert_eq!(query.rrclass, 1); |
840 | | |
841 | | assert_eq!(response.answers.len(), 1); |
842 | | |
843 | | let answer = &response.answers[0]; |
844 | | assert_eq!(answer.name.value, "vaaaakardli.pirate.sea".as_bytes().to_vec()); |
845 | | assert_eq!(answer.rrtype, DNS_RECORD_TYPE_NULL); |
846 | | assert_eq!(answer.rrclass, 1); |
847 | | assert_eq!(answer.ttl, 0); |
848 | | assert_eq!( |
849 | | answer.data, |
850 | | DNSRData::NULL(vec![ |
851 | | 0x56, 0x41, 0x43, 0x4b, /* VACK */ |
852 | | 0x44, 0x03, 0xc5, 0xe9, 0x01, /* D.... */ |
853 | | ]) |
854 | | ); |
855 | | } |
856 | | |
857 | | #[test] |
858 | | fn test_dns_parse_rdata_sshfp() { |
859 | | // Dummy data since we don't have a pcap sample. |
860 | | let data: &[u8] = &[ |
861 | | // algo: DSS |
862 | | 0x02, // fp_type: SHA-1 |
863 | | 0x01, // fingerprint: 123456789abcdef67890123456789abcdef67890 |
864 | | 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf6, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, |
865 | | 0x9a, 0xbc, 0xde, 0xf6, 0x78, 0x90, |
866 | | ]; |
867 | | |
868 | | let (rem, rdata) = dns_parse_rdata_sshfp(data).unwrap(); |
869 | | // The data should be fully parsed. |
870 | | assert_eq!(rem.len(), 0); |
871 | | |
872 | | match rdata { |
873 | | DNSRData::SSHFP(sshfp) => { |
874 | | assert_eq!(sshfp.algo, 2); |
875 | | assert_eq!(sshfp.fp_type, 1); |
876 | | assert_eq!(sshfp.fingerprint, &data[2..]); |
877 | | } |
878 | | _ => { |
879 | | panic!("Expected DNSRData::SSHFP"); |
880 | | } |
881 | | } |
882 | | } |
883 | | |
884 | | #[test] |
885 | | fn test_dns_parse_rdata_srv() { |
886 | | /* ; <<>> DiG 9.11.5-P4-5.1+deb10u2-Debian <<>> _sip._udp.sip.voice.google.com SRV |
887 | | ;; global options: +cmd |
888 | | ;; Got answer: |
889 | | ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1524 |
890 | | ;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 3 |
891 | | |
892 | | [...] |
893 | | |
894 | | ;; ANSWER SECTION: |
895 | | _sip._udp.sip.voice.google.com. 300 IN SRV 10 1 5060 sip-anycast-1.voice.google.com. |
896 | | _sip._udp.sip.voice.google.com. 300 IN SRV 20 1 5060 sip-anycast-2.voice.google.com. |
897 | | |
898 | | [...] |
899 | | |
900 | | ;; Query time: 72 msec |
901 | | ;; MSG SIZE rcvd: 191 */ |
902 | | |
903 | | let pkt: &[u8] = &[ |
904 | | 0xeb, 0x56, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x04, 0x5f, |
905 | | 0x73, 0x69, 0x70, 0x04, 0x5f, 0x75, 0x64, 0x70, 0x03, 0x73, 0x69, 0x70, 0x05, 0x76, |
906 | | 0x6f, 0x69, 0x63, 0x65, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, |
907 | | 0x6d, 0x00, 0x00, 0x21, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x21, 0x00, 0x01, 0x00, 0x00, |
908 | | 0x01, 0x13, 0x00, 0x26, 0x00, 0x14, 0x00, 0x01, 0x13, 0xc4, 0x0d, 0x73, 0x69, 0x70, |
909 | | 0x2d, 0x61, 0x6e, 0x79, 0x63, 0x61, 0x73, 0x74, 0x2d, 0x32, 0x05, 0x76, 0x6f, 0x69, |
910 | | 0x63, 0x65, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, |
911 | | 0xc0, 0x0c, 0x00, 0x21, 0x00, 0x01, 0x00, 0x00, 0x01, 0x13, 0x00, 0x26, 0x00, 0x0a, |
912 | | 0x00, 0x01, 0x13, 0xc4, 0x0d, 0x73, 0x69, 0x70, 0x2d, 0x61, 0x6e, 0x79, 0x63, 0x61, |
913 | | 0x73, 0x74, 0x2d, 0x31, 0x05, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x06, 0x67, 0x6f, 0x6f, |
914 | | 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, |
915 | | ]; |
916 | | |
917 | | let (rem, (response, _flags)) = dns_parse_response(pkt).unwrap(); |
918 | | // The data should be fully parsed. |
919 | | assert_eq!(rem.len(), 0); |
920 | | |
921 | | assert_eq!(response.answers.len(), 2); |
922 | | |
923 | | let answer1 = &response.answers[0]; |
924 | | match &answer1.data { |
925 | | DNSRData::SRV(srv) => { |
926 | | assert_eq!(srv.priority, 20); |
927 | | assert_eq!(srv.weight, 1); |
928 | | assert_eq!(srv.port, 5060); |
929 | | assert_eq!( |
930 | | srv.target.value, |
931 | | "sip-anycast-2.voice.google.com".as_bytes().to_vec() |
932 | | ); |
933 | | } |
934 | | _ => { |
935 | | panic!("Expected DNSRData::SRV"); |
936 | | } |
937 | | } |
938 | | let answer2 = &response.answers[1]; |
939 | | match &answer2.data { |
940 | | DNSRData::SRV(srv) => { |
941 | | assert_eq!(srv.priority, 10); |
942 | | assert_eq!(srv.weight, 1); |
943 | | assert_eq!(srv.port, 5060); |
944 | | assert_eq!( |
945 | | srv.target.value, |
946 | | "sip-anycast-1.voice.google.com".as_bytes().to_vec() |
947 | | ); |
948 | | } |
949 | | _ => { |
950 | | panic!("Expected DNSRData::SRV"); |
951 | | } |
952 | | } |
953 | | } |
954 | | |
955 | | #[test] |
956 | | fn test_dns_parse_name_truncated() { |
957 | | // Generate a non-compressed hostname over our maximum of 1024. |
958 | | let mut buf: Vec<u8> = vec![]; |
959 | | |
960 | | for i in 1..18 { |
961 | | buf.push(0b0011_1111); |
962 | | buf.resize(i * 64, b'a'); |
963 | | } |
964 | | |
965 | | let mut flags = DNSNameFlags::default(); |
966 | | let (rem, name) = dns_parse_name(&buf, &buf, &mut flags).unwrap(); |
967 | | assert_eq!(name.value.len(), MAX_NAME_LEN); |
968 | | assert!(name.flags.contains(DNSNameFlags::TRUNCATED)); |
969 | | assert!(rem.is_empty()); |
970 | | } |
971 | | |
972 | | #[test] |
973 | | fn test_dns_parse_name_truncated_max_segments_no_pointer() { |
974 | | let mut buf: Vec<u8> = vec![]; |
975 | | for _ in 0..256 { |
976 | | buf.push(0b0000_0001); |
977 | | buf.push(b'a'); |
978 | | } |
979 | | |
980 | | // This should fail as we've hit the segment limit without a |
981 | | // pointer, we'd need to keep parsing more segments to figure |
982 | | // out where the next data point lies. |
983 | | let mut flags = DNSNameFlags::default(); |
984 | | assert!(dns_parse_name(&buf, &buf, &mut flags).is_err()); |
985 | | } |
986 | | |
987 | | #[test] |
988 | | fn test_dns_parse_name_truncated_max_segments_with_pointer() { |
989 | | #[rustfmt::skip] |
990 | | let buf: Vec<u8> = vec![ |
991 | | // "a" at the beginning of the buffer. |
992 | | 0b0000_0001, |
993 | | b'a', |
994 | | |
995 | | // Followed by a pointer back to the beginning. |
996 | | 0b1100_0000, |
997 | | 0b0000_0000, |
998 | | |
999 | | // The start of the name, which is pointer to the beginning of |
1000 | | // the buffer. |
1001 | | 0b1100_0000, |
1002 | | 0b000_0000 |
1003 | | ]; |
1004 | | |
1005 | | let mut flags = DNSNameFlags::default(); |
1006 | | let (_rem, name) = dns_parse_name(&buf[4..], &buf, &mut flags).unwrap(); |
1007 | | assert_eq!(name.value.len(), 255); |
1008 | | assert!(name.flags.contains(DNSNameFlags::TRUNCATED)); |
1009 | | } |
1010 | | |
1011 | | #[test] |
1012 | | fn test_dns_parse_name_self_reference() { |
1013 | | let buf = vec![0b1100_0000, 0b0000_0000]; |
1014 | | let mut flags = DNSNameFlags::default(); |
1015 | | assert!(dns_parse_name(&buf, &buf, &mut flags).is_err()); |
1016 | | } |
1017 | | } |