/src/freeradius-server/src/protocols/dns/decode.c
Line | Count | Source |
1 | | /* |
2 | | * This library is free software; you can redistribute it and/or |
3 | | * modify it under the terms of the GNU Lesser General Public |
4 | | * License as published by the Free Software Foundation; either |
5 | | * version 2.1 of the License, or (at your option) any later version. |
6 | | * |
7 | | * This library 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 GNU |
10 | | * Lesser General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU Lesser General Public |
13 | | * License along with this library; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** |
18 | | * $Id: 3ad88d6f595caac176e5d8f76c83cf24a3cb2e45 $ |
19 | | * |
20 | | * @file protocols/dns/decode.c |
21 | | * @brief Functions to decode DNS packets. |
22 | | * |
23 | | * @author Alan DeKok (aland@freeradius.org) |
24 | | * |
25 | | * @copyright 2021 The FreeRADIUS server project |
26 | | * @copyright 2021 NetworkRADIUS SARL (legal@networkradius.com) |
27 | | */ |
28 | | #include <freeradius-devel/io/test_point.h> |
29 | | #include <freeradius-devel/util/dns.h> |
30 | | #include <freeradius-devel/util/proto.h> |
31 | | #include <freeradius-devel/util/struct.h> |
32 | | |
33 | | #include "dns.h" |
34 | | #include "attrs.h" |
35 | | |
36 | | static ssize_t decode_option(TALLOC_CTX *ctx, fr_pair_list_t *out, |
37 | | fr_dict_attr_t const *parent, |
38 | | uint8_t const *data, size_t const data_len, void *decode_ctx); |
39 | | |
40 | | static ssize_t decode_value(TALLOC_CTX *ctx, fr_pair_list_t *out, |
41 | | fr_dict_attr_t const *parent, |
42 | | uint8_t const *data, size_t const data_len, void *decode_ctx); |
43 | | |
44 | | static ssize_t decode_tlv_trampoline(TALLOC_CTX *ctx, fr_pair_list_t *out, |
45 | | fr_dict_attr_t const *parent, |
46 | | uint8_t const *data, size_t const data_len, void *decode_ctx) |
47 | 1.39k | { |
48 | 1.39k | return fr_pair_tlvs_from_network(ctx, out, parent, data, data_len, decode_ctx, decode_option, NULL, true); |
49 | 1.39k | } |
50 | | |
51 | | |
52 | | static ssize_t decode_value_trampoline(TALLOC_CTX *ctx, fr_pair_list_t *out, |
53 | | fr_dict_attr_t const *parent, |
54 | | uint8_t const *data, size_t const data_len, void *decode_ctx) |
55 | 139k | { |
56 | 139k | if ((parent->type == FR_TYPE_STRING) && fr_dns_flag_dns_label(parent)) { |
57 | 36.5k | fr_dns_ctx_t *packet_ctx = decode_ctx; |
58 | | |
59 | 36.5k | return fr_pair_dns_labels_from_network(ctx, out, parent, packet_ctx->packet, data, data_len, packet_ctx->lb, false); |
60 | 36.5k | } |
61 | | |
62 | 103k | return decode_value(ctx, out, parent, data, data_len, decode_ctx); |
63 | 139k | } |
64 | | |
65 | | |
66 | | static ssize_t decode_value(TALLOC_CTX *ctx, fr_pair_list_t *out, |
67 | | fr_dict_attr_t const *parent, |
68 | | uint8_t const *data, size_t const data_len, void *decode_ctx) |
69 | 118k | { |
70 | 118k | ssize_t slen; |
71 | 118k | fr_pair_t *vp; |
72 | 118k | uint8_t prefix_len; |
73 | | |
74 | 118k | FR_PROTO_HEX_DUMP(data, data_len, "decode_value"); |
75 | | |
76 | 118k | switch (parent->type) { |
77 | | /* |
78 | | * Address MAY be shorter than 16 bytes. |
79 | | */ |
80 | 0 | case FR_TYPE_IPV6_PREFIX: |
81 | 0 | if ((data_len == 0) || (data_len > (1 + sizeof(vp->vp_ipv6addr)))) { |
82 | 5.34k | raw: |
83 | 5.34k | return fr_pair_raw_from_network(ctx, out, parent, data, data_len); |
84 | |
|
85 | 0 | } |
86 | | |
87 | | /* |
88 | | * Structs used fixed length fields |
89 | | */ |
90 | 0 | if (parent->parent->type == FR_TYPE_STRUCT) { |
91 | 0 | if (data_len != (1 + sizeof(vp->vp_ipv6addr))) goto raw; |
92 | | |
93 | 0 | vp = fr_pair_afrom_da(ctx, parent); |
94 | 0 | if (!vp) return PAIR_DECODE_OOM; |
95 | | |
96 | 0 | vp->vp_ip.af = AF_INET6; |
97 | 0 | vp->vp_ip.prefix = data[0]; |
98 | 0 | memcpy(&vp->vp_ipv6addr, data + 1, data_len - 1); |
99 | 0 | break; |
100 | 0 | } |
101 | | |
102 | | /* |
103 | | * No address, the prefix length MUST be zero. |
104 | | */ |
105 | 0 | if (data_len == 1) { |
106 | 0 | if (data[0] != 0) goto raw; |
107 | | |
108 | 0 | vp = fr_pair_afrom_da(ctx, parent); |
109 | 0 | if (!vp) return PAIR_DECODE_OOM; |
110 | | |
111 | 0 | vp->vp_ip.af = AF_INET6; |
112 | 0 | break; |
113 | 0 | } |
114 | | |
115 | 0 | prefix_len = data[0]; |
116 | | |
117 | | /* |
118 | | * If we have a /64 prefix but only 7 bytes of |
119 | | * address, that's an error. |
120 | | */ |
121 | 0 | if (fr_bytes_from_bits(prefix_len) > (data_len - 1)) goto raw; |
122 | | |
123 | 0 | vp = fr_pair_afrom_da(ctx, parent); |
124 | 0 | if (!vp) return PAIR_DECODE_OOM; |
125 | | |
126 | 0 | vp->vp_ip.af = AF_INET6; |
127 | 0 | vp->vp_ip.prefix = prefix_len; |
128 | 0 | memcpy(&vp->vp_ipv6addr, data + 1, data_len - 1); |
129 | 0 | break; |
130 | | |
131 | | /* |
132 | | * A bool is encoded as an empty option if it's |
133 | | * true. A bool is omitted entirely if it's |
134 | | * false. |
135 | | */ |
136 | 662 | case FR_TYPE_BOOL: |
137 | 662 | if (data_len != 0) goto raw; |
138 | 473 | vp = fr_pair_afrom_da(ctx, parent); |
139 | 473 | if (!vp) return PAIR_DECODE_OOM; |
140 | 473 | vp->vp_bool = true; |
141 | 473 | break; |
142 | | |
143 | 7.99k | case FR_TYPE_STRUCT: |
144 | 7.99k | slen = fr_struct_from_network(ctx, out, parent, data, data_len, |
145 | 7.99k | decode_ctx, decode_value_trampoline, NULL); |
146 | 7.99k | if (slen < 0) return slen; |
147 | 7.94k | return data_len; |
148 | | |
149 | 0 | case FR_TYPE_GROUP: |
150 | 0 | return PAIR_DECODE_FATAL_ERROR; /* not supported */ |
151 | | |
152 | 109k | default: |
153 | 109k | vp = fr_pair_afrom_da(ctx, parent); |
154 | 109k | if (!vp) return PAIR_DECODE_OOM; |
155 | | |
156 | 109k | if (fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da, |
157 | 109k | &FR_DBUFF_TMP(data, data_len), data_len, true) < 0) { |
158 | 5.15k | FR_PROTO_TRACE("failed decoding?"); |
159 | 5.15k | talloc_free(vp); |
160 | 5.15k | goto raw; |
161 | 5.15k | } |
162 | 104k | break; |
163 | 118k | } |
164 | | |
165 | 104k | vp->vp_tainted = true; |
166 | 104k | fr_pair_append(out, vp); |
167 | 104k | return data_len; |
168 | 118k | } |
169 | | |
170 | | |
171 | 21.4k | #define DNS_GET_OPTION_NUM(_x) fr_nbo_to_uint16(_x) |
172 | 21.4k | #define DNS_GET_OPTION_LEN(_x) fr_nbo_to_uint16((_x) + 2) |
173 | | |
174 | | static ssize_t decode_option(TALLOC_CTX *ctx, fr_pair_list_t *out, |
175 | | fr_dict_attr_t const *parent, |
176 | | uint8_t const *data, size_t const data_len, void *decode_ctx) |
177 | 21.4k | { |
178 | 21.4k | unsigned int option; |
179 | 21.4k | size_t len; |
180 | 21.4k | ssize_t slen; |
181 | 21.4k | fr_dict_attr_t const *da; |
182 | 21.4k | fr_dns_ctx_t *packet_ctx = decode_ctx; |
183 | | |
184 | | #ifdef STATIC_ANALYZER |
185 | | if (!packet_ctx || !packet_ctx->tmp_ctx) return PAIR_DECODE_FATAL_ERROR; |
186 | | #endif |
187 | | |
188 | | /* |
189 | | * Must have at least an option header. |
190 | | */ |
191 | 21.4k | if (data_len < 4) { |
192 | 15 | fr_strerror_printf("%s: Insufficient data", __FUNCTION__); |
193 | 15 | return -(data_len); |
194 | 15 | } |
195 | | |
196 | 21.4k | option = DNS_GET_OPTION_NUM(data); |
197 | 21.4k | len = DNS_GET_OPTION_LEN(data); |
198 | 21.4k | if (len > (data_len - 4)) { |
199 | 50 | fr_strerror_printf("%s: Option overflows input. " |
200 | 50 | "Optional length must be less than %zu bytes, got %zu bytes", |
201 | 50 | __FUNCTION__, data_len - 4, len); |
202 | 50 | return PAIR_DECODE_FATAL_ERROR; |
203 | 50 | } |
204 | | |
205 | 21.3k | FR_PROTO_HEX_DUMP(data, len + 4, "decode_option"); |
206 | | |
207 | 21.3k | da = fr_dict_attr_child_by_num(parent, option); |
208 | 21.3k | if (!da) { |
209 | 7.60k | da = fr_dict_attr_unknown_raw_afrom_num(packet_ctx->tmp_ctx, parent, option); |
210 | 7.60k | if (!da) return PAIR_DECODE_FATAL_ERROR; |
211 | 7.60k | } |
212 | 21.3k | FR_PROTO_TRACE("decode context changed %s -> %s",da->parent->name, da->name); |
213 | | |
214 | 21.3k | if ((da->type == FR_TYPE_STRING) && fr_dns_flag_dns_label(da)) { |
215 | 0 | slen = fr_pair_dns_labels_from_network(ctx, out, da, packet_ctx->packet, data + 4, len, packet_ctx->lb, true); |
216 | |
|
217 | 21.3k | } else if (da->flags.array) { |
218 | 0 | slen = fr_pair_array_from_network(ctx, out, da, data + 4, len, decode_ctx, decode_value); |
219 | |
|
220 | 21.3k | } else { |
221 | 21.3k | slen = decode_value(ctx, out, da, data + 4, len, decode_ctx); |
222 | 21.3k | } |
223 | 21.3k | fr_dict_attr_unknown_free(&da); |
224 | | |
225 | 21.3k | if (slen < 0) return slen; |
226 | | |
227 | 21.3k | return len + 4; |
228 | 21.3k | } |
229 | | |
230 | | static ssize_t decode_record(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *attr, |
231 | | uint8_t const *rr, uint8_t const *end, |
232 | | fr_dns_ctx_t *packet_ctx, uint8_t const *counter) |
233 | 5.31k | { |
234 | 5.31k | unsigned int i, count; |
235 | 5.31k | uint8_t const *p = rr; |
236 | | |
237 | | /* |
238 | | * The header has a count of how many records we need to decode. |
239 | | */ |
240 | 5.31k | count = fr_nbo_to_uint16(counter); |
241 | 5.31k | FR_PROTO_TRACE("Decoding %u of %s", count, attr->name); |
242 | | |
243 | | /* coverity[tainted_data] */ |
244 | 36.4k | for (i = 0; (i < count) && (p < end); i++) { |
245 | 31.1k | ssize_t slen; |
246 | | |
247 | 31.1k | FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - %s %u/%u", attr->name, i, count); |
248 | | |
249 | 31.1k | slen = fr_struct_from_network(ctx, out, attr, p, end - p, |
250 | 31.1k | packet_ctx, decode_value_trampoline, decode_tlv_trampoline); |
251 | 31.1k | if (slen < 0) return slen; |
252 | 31.1k | if (!slen) break; |
253 | | |
254 | 31.1k | fr_assert(slen <= (end - p)); |
255 | | |
256 | 31.1k | p += slen; |
257 | 31.1k | } |
258 | | |
259 | 5.31k | return p - rr; |
260 | 5.31k | } |
261 | | |
262 | | /** Decode a DNS packet |
263 | | * |
264 | | */ |
265 | | ssize_t fr_dns_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *packet, size_t packet_len, fr_dns_ctx_t *packet_ctx) |
266 | 1.33k | { |
267 | 1.33k | ssize_t slen; |
268 | 1.33k | uint8_t const *p, *end; |
269 | | |
270 | 1.33k | if (packet_len < DNS_HDR_LEN) return 0; |
271 | | |
272 | | /* |
273 | | * @todo - synthesize Packet-Type from the various fields. |
274 | | */ |
275 | | |
276 | 1.33k | FR_PROTO_HEX_DUMP(packet, packet_len, "fr_dns_decode"); |
277 | | |
278 | | /* |
279 | | * Decode the header. |
280 | | */ |
281 | 1.33k | slen = fr_struct_from_network(ctx, out, attr_dns_packet, packet, DNS_HDR_LEN, |
282 | 1.33k | packet_ctx, decode_value_trampoline, NULL); /* no TLVs in the header */ |
283 | 1.33k | if (slen < 0) { |
284 | 0 | fr_strerror_printf("Failed decoding DNS header - %s", fr_strerror()); |
285 | 0 | return slen; |
286 | 0 | } |
287 | 1.33k | fr_assert(slen == DNS_HDR_LEN); |
288 | | |
289 | 1.33k | p = packet + DNS_HDR_LEN; |
290 | 1.33k | end = packet + packet_len; |
291 | 1.33k | FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after header"); |
292 | | |
293 | 1.33k | slen = decode_record(ctx, out, attr_dns_question, p, end, packet_ctx, packet + 4); |
294 | 1.33k | if (slen < 0) { |
295 | 0 | fr_strerror_printf("Failed decoding questions - %s", fr_strerror()); |
296 | 0 | return slen; |
297 | 0 | } |
298 | 1.33k | p += slen; |
299 | 1.33k | FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after %zd bytes of questions", slen); |
300 | | |
301 | 1.33k | slen = decode_record(ctx, out, attr_dns_rr, p, end, packet_ctx, packet + 6); |
302 | 1.33k | if (slen < 0) { |
303 | 1 | fr_strerror_printf("Failed decoding RRs - %s", fr_strerror()); |
304 | 1 | return slen - (p - packet); |
305 | 1 | } |
306 | 1.32k | p += slen; |
307 | 1.32k | FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after %zd bytes of RRs", slen); |
308 | | |
309 | 1.32k | slen = decode_record(ctx, out, attr_dns_ns, p, end, packet_ctx, packet + 8); |
310 | 1.32k | if (slen < 0) { |
311 | 2 | fr_strerror_printf("Failed decoding NS - %s", fr_strerror()); |
312 | 2 | return slen - (p - packet); |
313 | 2 | } |
314 | 1.32k | p += slen; |
315 | 1.32k | FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after %zd bytes of NS", slen); |
316 | | |
317 | 1.32k | slen = decode_record(ctx, out, attr_dns_ar, p, end, packet_ctx, packet + 10); |
318 | 1.32k | if (slen < 0) { |
319 | 2 | fr_strerror_printf("Failed decoding additional records - %s", fr_strerror()); |
320 | 2 | return slen - (p - packet); |
321 | 2 | } |
322 | 1.32k | FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after %zd bytes of additional records", slen); |
323 | | |
324 | | // p += slen; |
325 | | |
326 | 1.32k | return packet_len; |
327 | 1.32k | } |
328 | | |
329 | | /** Decode DNS RR |
330 | | * |
331 | | * @param[in] ctx context to alloc new attributes in. |
332 | | * @param[in,out] out Where to write the decoded options. |
333 | | * @param[in] parent to lookup attributes in. |
334 | | * @param[in] data to parse. |
335 | | * @param[in] data_len of data to parse. |
336 | | * @param[in] decode_ctx Unused. |
337 | | */ |
338 | | static ssize_t decode_rr(TALLOC_CTX *ctx, fr_pair_list_t *out, UNUSED fr_dict_attr_t const *parent, |
339 | | uint8_t const *data, size_t data_len, void *decode_ctx) |
340 | 0 | { |
341 | 0 | ssize_t slen; |
342 | 0 | fr_dns_ctx_t *packet_ctx = (fr_dns_ctx_t *) decode_ctx; |
343 | |
|
344 | 0 | FR_PROTO_TRACE("%s called to parse %zu byte(s)", __FUNCTION__, data_len); |
345 | |
|
346 | 0 | if (data_len == 0) return 0; |
347 | | |
348 | | /* |
349 | | * This function is only used for testing, so update decode_ctx |
350 | | */ |
351 | 0 | packet_ctx->packet = data; |
352 | 0 | packet_ctx->packet_len = data_len; |
353 | |
|
354 | 0 | FR_PROTO_HEX_DUMP(data, data_len, NULL); |
355 | | |
356 | | /* |
357 | | * There should be at least room for the RR header |
358 | | */ |
359 | 0 | if (data_len < 9) { |
360 | 0 | fr_strerror_printf("%s: Insufficient data", __FUNCTION__); |
361 | 0 | return -1; |
362 | 0 | } |
363 | | |
364 | 0 | slen = fr_struct_from_network(ctx, out, attr_dns_rr, data, data_len, |
365 | 0 | decode_ctx, decode_value_trampoline, decode_tlv_trampoline); |
366 | 0 | if (slen < 0) return slen; |
367 | | |
368 | 0 | FR_PROTO_TRACE("decoding option complete, returning %zd byte(s)", slen); |
369 | 0 | return slen; |
370 | 0 | } |
371 | | |
372 | | /* |
373 | | * Test points |
374 | | */ |
375 | | static int decode_test_ctx(void **out, TALLOC_CTX *ctx, UNUSED fr_dict_t const *dict, |
376 | | UNUSED fr_dict_attr_t const *root_da) |
377 | 3.24k | { |
378 | 3.24k | fr_dns_ctx_t *test_ctx; |
379 | | |
380 | 3.24k | test_ctx = talloc_zero(ctx, fr_dns_ctx_t); |
381 | | |
382 | 3.24k | test_ctx->tmp_ctx = talloc(test_ctx, uint8_t); |
383 | 3.24k | *out = test_ctx; |
384 | | |
385 | 3.24k | return 0; |
386 | 3.24k | } |
387 | | |
388 | | fr_table_num_ordered_t fr_dns_reason_fail_table[] = { |
389 | | { L("none"), FR_DNS_DECODE_FAIL_NONE }, |
390 | | { L("packet is smaller than DNS header"), FR_DNS_DECODE_FAIL_MIN_LENGTH_PACKET }, |
391 | | { L("packet is larger than 65535"), FR_DNS_DECODE_FAIL_MAX_LENGTH_PACKET }, |
392 | | { L("expected query / answer, got answer / query"), FR_DNS_DECODE_FAIL_UNEXPECTED }, |
393 | | { L("no 'questions' in query packet"), FR_DNS_DECODE_FAIL_NO_QUESTIONS }, |
394 | | { L("unexprected answers in query packet"), FR_DNS_DECODE_FAIL_ANSWERS_IN_QUESTION }, |
395 | | { L("unexpected NS records in query packet"), FR_DNS_DECODE_FAIL_NS_IN_QUESTION }, |
396 | | { L("invalid label for resource record"), FR_DNS_DECODE_FAIL_INVALID_RR_LABEL }, |
397 | | { L("missing resource record header"), FR_DNS_DECODE_FAIL_MISSING_RR_HEADER }, |
398 | | { L("missing resource record length field"), FR_DNS_DECODE_FAIL_MISSING_RR_LEN }, |
399 | | { L("resource record length field is zero"), FR_DNS_DECODE_FAIL_ZERO_RR_LEN }, |
400 | | { L("resource record length overflows the packet"), FR_DNS_DECODE_FAIL_RR_OVERFLOWS_PACKET }, |
401 | | { L("more resource records than indicated in header"), FR_DNS_DECODE_FAIL_TOO_MANY_RRS }, |
402 | | { L("fewer resource records than indicated in header"), FR_DNS_DECODE_FAIL_TOO_FEW_RRS }, |
403 | | { L("pointer overflows packet"), FR_DNS_DECODE_FAIL_POINTER_OVERFLOWS_PACKET }, |
404 | | { L("pointer points to packet header"), FR_DNS_DECODE_FAIL_POINTER_TO_HEADER }, |
405 | | { L("pointer does not point to a label"), FR_DNS_DECODE_FAIL_POINTER_TO_NON_LABEL }, |
406 | | { L("pointer creates a loop"), FR_DNS_DECODE_FAIL_POINTER_LOOPS }, |
407 | | { L("invalid pointer"), FR_DNS_DECODE_FAIL_INVALID_POINTER }, |
408 | | { L("label overflows the packet"), FR_DNS_DECODE_FAIL_LABEL_OVERFLOWS_PACKET }, |
409 | | { L("too many characters in label"), FR_DNS_DECODE_FAIL_LABEL_TOO_LONG }, |
410 | | { L("query record header is missing"), FR_DNS_DECODE_FAIL_MISSING_QD_HEADER }, |
411 | | { L("missing TLV header in OPT RR"), FR_DNS_DECODE_FAIL_MISSING_TLV_HEADER }, |
412 | | { L("TLV overflows enclosing RR"), FR_DNS_DECODE_FAIL_TLV_OVERFLOWS_RR }, |
413 | | }; |
414 | | size_t fr_dns_reason_fail_table_len = NUM_ELEMENTS(fr_dns_reason_fail_table); |
415 | | |
416 | | static ssize_t decode_proto(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *data, size_t data_len, void *proto_ctx) |
417 | 1.61k | { |
418 | 1.61k | fr_dns_ctx_t *packet_ctx = proto_ctx; |
419 | 1.61k | fr_dns_decode_fail_t reason; |
420 | | |
421 | 1.61k | if (data_len > 65535) return -1; /* packet is too big */ |
422 | | |
423 | | /* |
424 | | * Allow queries or answers |
425 | | */ |
426 | 1.60k | if (!fr_dns_packet_ok(data, data_len, true, &reason)) { |
427 | 1.48k | if (reason != FR_DNS_DECODE_FAIL_UNEXPECTED) goto fail; |
428 | | |
429 | 1.41k | if (!fr_dns_packet_ok(data, data_len, false, &reason)) { |
430 | 274 | fail: |
431 | 274 | fr_strerror_printf("DNS packet malformed - %s", |
432 | 274 | fr_table_str_by_value(fr_dns_reason_fail_table, reason, "<INVALID>")); |
433 | 274 | return -1; |
434 | 202 | } |
435 | 1.41k | } |
436 | | |
437 | 1.33k | packet_ctx->packet = data; |
438 | 1.33k | packet_ctx->packet_len = data_len; |
439 | 1.33k | packet_ctx->lb = fr_dns_labels_get(data, data_len, true); |
440 | 1.33k | fr_assert(packet_ctx->lb != NULL); |
441 | | |
442 | 1.33k | return fr_dns_decode(ctx, out, data, data_len, packet_ctx); |
443 | 1.60k | } |
444 | | |
445 | | /* |
446 | | * Test points |
447 | | */ |
448 | | extern fr_test_point_pair_decode_t dns_tp_decode_pair; |
449 | | fr_test_point_pair_decode_t dns_tp_decode_pair = { |
450 | | .test_ctx = decode_test_ctx, |
451 | | .func = decode_rr |
452 | | }; |
453 | | |
454 | | extern fr_test_point_proto_decode_t dns_tp_decode_proto; |
455 | | fr_test_point_proto_decode_t dns_tp_decode_proto = { |
456 | | .test_ctx = decode_test_ctx, |
457 | | .func = decode_proto |
458 | | }; |