/src/freeradius-server/src/protocols/dhcpv6/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: 85e1cdf59923173579798d0f0ce5826c3386c2d8 $ |
19 | | * |
20 | | * @file protocols/dhcpv6/decode.c |
21 | | * @brief Functions to decode DHCP options. |
22 | | * |
23 | | * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org) |
24 | | * |
25 | | * @copyright 2018 The FreeRADIUS server project |
26 | | * @copyright 2018 NetworkRADIUS SARL (legal@networkradius.com) |
27 | | */ |
28 | | #include <stdint.h> |
29 | | #include <stddef.h> |
30 | | |
31 | | #include <freeradius-devel/io/test_point.h> |
32 | | #include <freeradius-devel/util/dns.h> |
33 | | #include <freeradius-devel/util/proto.h> |
34 | | #include <freeradius-devel/util/struct.h> |
35 | | |
36 | | #include "dhcpv6.h" |
37 | | #include "attrs.h" |
38 | | |
39 | | /* |
40 | | * RFC 8415 Section 21.7 defines option codes that MUST NOT appear |
41 | | * in an Option Request option. We convert the attributes to a |
42 | | * 64-bit number which can be easily checked. |
43 | | */ |
44 | | #define DHCPV6_ORO_FORBIDDEN \ |
45 | 99.4k | ((1ULL << 1) | /* OPTION_CLIENTID */ \ |
46 | 99.4k | (1ULL << 2) | /* OPTION_SERVERID */ \ |
47 | 99.4k | (1ULL << 3) | /* OPTION_IA_NA */ \ |
48 | 99.4k | (1ULL << 4) | /* OPTION_IA_TA */ \ |
49 | 99.4k | (1ULL << 5) | /* OPTION_IAADDR */ \ |
50 | 99.4k | (1ULL << 6) | /* OPTION_ORO */ \ |
51 | 99.4k | (1ULL << 7) | /* OPTION_PREFERENCE */ \ |
52 | 99.4k | (1ULL << 8) | /* OPTION_ELAPSED_TIME */ \ |
53 | 99.4k | (1ULL << 9) | /* OPTION_RELAY_MSG */ \ |
54 | 99.4k | (1ULL << 11) | /* OPTION_AUTH */ \ |
55 | 99.4k | (1ULL << 12) | /* OPTION_UNICAST */ \ |
56 | 99.4k | (1ULL << 13) | /* OPTION_STATUS_CODE */ \ |
57 | 99.4k | (1ULL << 14) | /* OPTION_RAPID_COMMIT */ \ |
58 | 99.4k | (1ULL << 15) | /* OPTION_USER_CLASS */ \ |
59 | 99.4k | (1ULL << 16) | /* OPTION_VENDOR_CLASS */ \ |
60 | 99.4k | (1ULL << 18) | /* OPTION_INTERFACE_ID */ \ |
61 | 99.4k | (1ULL << 19) | /* OPTION_RECONF_MSG */ \ |
62 | 99.4k | (1ULL << 20) | /* OPTION_RECONF_ACCEPT */ \ |
63 | 99.4k | (1ULL << 25) | /* OPTION_IA_PD */ \ |
64 | 99.4k | (1ULL << 26)) /* OPTION_IAPREFIX */ |
65 | | |
66 | | static ssize_t decode_option(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 | | |
70 | | static ssize_t decode_tlv_trampoline(TALLOC_CTX *ctx, fr_pair_list_t *out, |
71 | | fr_dict_attr_t const *parent, |
72 | | uint8_t const *data, size_t const data_len, void *decode_ctx) |
73 | 1.92k | { |
74 | 1.92k | return fr_pair_tlvs_from_network(ctx, out, parent, data, data_len, decode_ctx, decode_option, NULL, true); |
75 | 1.92k | } |
76 | | |
77 | | static ssize_t decode_value(TALLOC_CTX *ctx, fr_pair_list_t *out, |
78 | | fr_dict_attr_t const *parent, |
79 | | uint8_t const *data, size_t const data_len, void *decode_ctx); |
80 | | |
81 | | /** Handle arrays of DNS labels for fr_struct_from_network() |
82 | | * |
83 | | */ |
84 | | static ssize_t decode_value_trampoline(TALLOC_CTX *ctx, fr_pair_list_t *out, |
85 | | fr_dict_attr_t const *parent, |
86 | | uint8_t const *data, size_t const data_len, void *decode_ctx) |
87 | 125k | { |
88 | 125k | if ((parent->type == FR_TYPE_STRING) && fr_dhcpv6_flag_any_dns_label(parent)) { |
89 | 26.9k | return fr_pair_dns_labels_from_network(ctx, out, parent, data, data, data_len, NULL, false); |
90 | 26.9k | } |
91 | | |
92 | 98.4k | return decode_value(ctx, out, parent, data, data_len, decode_ctx); |
93 | 125k | } |
94 | | |
95 | | |
96 | | static ssize_t decode_value(TALLOC_CTX *ctx, fr_pair_list_t *out, |
97 | | fr_dict_attr_t const *parent, |
98 | | uint8_t const *data, size_t const data_len, void *decode_ctx) |
99 | 549k | { |
100 | 549k | ssize_t slen; |
101 | 549k | fr_pair_t *vp = NULL; |
102 | 549k | fr_dict_attr_t const *ref; |
103 | | |
104 | 549k | FR_PROTO_HEX_DUMP(data, data_len, "decode_value"); |
105 | | |
106 | 549k | switch (parent->type) { |
107 | 338k | case FR_TYPE_ATTR: |
108 | | /* |
109 | | * Force the length of the data to be two, |
110 | | * otherwise the "from network" call complains. |
111 | | * Because we pass in the enumv as the _parent_ |
112 | | * and not the da. The da is marked as "array", |
113 | | * but the parent is not. |
114 | | */ |
115 | 338k | if (data_len < 2) goto raw; |
116 | | |
117 | | /* |
118 | | * Run-time checks rather than assertions. |
119 | | */ |
120 | 338k | if (!parent->parent->flags.is_root) return -1; |
121 | | |
122 | 338k | vp = fr_pair_afrom_da(ctx, parent); |
123 | 338k | if (!vp) return PAIR_DECODE_OOM; |
124 | 338k | PAIR_ALLOCED(vp); |
125 | | |
126 | 338k | slen = fr_value_box_from_network(vp, &vp->data, vp->vp_type, parent->parent, |
127 | 338k | &FR_DBUFF_TMP(data, 2), 2, true); |
128 | 338k | if (slen <= 0) { |
129 | 0 | TALLOC_FREE(vp); |
130 | 0 | goto raw; |
131 | 0 | } |
132 | | |
133 | 338k | vp->vp_tainted = true; |
134 | 338k | fr_pair_append(out, vp); |
135 | 338k | return 2; |
136 | | |
137 | | /* |
138 | | * Address MAY be shorter than 16 bytes. |
139 | | */ |
140 | 2.72k | case FR_TYPE_IPV6_PREFIX: |
141 | 2.72k | if (data_len == 0) { |
142 | 30.2k | raw: |
143 | 30.2k | return fr_pair_raw_from_network(ctx, out, parent, data, data_len); |
144 | | |
145 | 465 | } |
146 | | |
147 | 2.25k | vp = fr_pair_afrom_da(ctx, parent); |
148 | 2.25k | if (!vp) return PAIR_DECODE_OOM; |
149 | 2.25k | PAIR_ALLOCED(vp); |
150 | | |
151 | 2.25k | slen = fr_value_box_ipaddr_from_network(&vp->data, parent->type, parent, |
152 | 2.25k | data[0], data + 1, data_len - 1, |
153 | 2.25k | (parent->parent->type == FR_TYPE_STRUCT), true); |
154 | 2.25k | if (slen < 0) goto raw_free; |
155 | | |
156 | 1.07k | slen++; /* account for the prefix */ |
157 | 1.07k | break; |
158 | | |
159 | | /* |
160 | | * A bool is encoded as an empty option if it's |
161 | | * true. A bool is omitted entirely if it's |
162 | | * false. |
163 | | */ |
164 | 623 | case FR_TYPE_BOOL: |
165 | 623 | if (data_len != 0) goto raw; |
166 | 301 | vp = fr_pair_afrom_da(ctx, parent); |
167 | 301 | if (!vp) return PAIR_DECODE_OOM; |
168 | 301 | PAIR_ALLOCED(vp); |
169 | | |
170 | 301 | vp->vp_bool = true; |
171 | 301 | slen = 0; |
172 | 301 | break; |
173 | | |
174 | | /* |
175 | | * A standard 32bit integer, but unlike normal UNIX timestamps |
176 | | * starts from the 1st of January 2000. |
177 | | * |
178 | | * In the encoder we subtract 30 years to any values, so |
179 | | * here we need to add that to the time here. |
180 | | */ |
181 | 1.62k | case FR_TYPE_DATE: |
182 | 1.62k | vp = fr_pair_afrom_da(ctx, parent); |
183 | 1.62k | if (!vp) return PAIR_DECODE_OOM; |
184 | 1.62k | PAIR_ALLOCED(vp); |
185 | | |
186 | 1.62k | slen = fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da, |
187 | 1.62k | &FR_DBUFF_TMP(data, data_len), data_len, true); |
188 | 1.62k | if (slen < 0) { |
189 | 1.18k | talloc_free(vp); |
190 | 1.18k | goto raw; |
191 | 1.18k | } |
192 | 442 | vp->vp_date = fr_unix_time_add(vp->vp_date, fr_time_delta_from_sec(DHCPV6_DATE_OFFSET)); |
193 | 442 | break; |
194 | | |
195 | 14.9k | case FR_TYPE_STRUCT: |
196 | 14.9k | slen = fr_struct_from_network(ctx, out, parent, data, data_len, |
197 | 14.9k | decode_ctx, decode_value_trampoline, decode_tlv_trampoline); |
198 | 14.9k | if (slen < 0) goto raw; |
199 | | |
200 | 8.77k | if (parent->flags.array) return slen; |
201 | 8.77k | return data_len; |
202 | | |
203 | 33.0k | case FR_TYPE_GROUP: |
204 | 33.0k | vp = fr_pair_afrom_da(ctx, parent); |
205 | 33.0k | if (!vp) return PAIR_DECODE_OOM; |
206 | 33.0k | PAIR_ALLOCED(vp); |
207 | | |
208 | 33.0k | ref = fr_dict_attr_ref(parent); |
209 | 33.0k | if (ref && (ref->dict != dict_dhcpv6)) { |
210 | 28.8k | fr_dict_protocol_t const *proto; |
211 | | |
212 | 28.8k | proto = fr_dict_protocol(ref->dict); |
213 | 28.8k | fr_assert(proto != NULL); |
214 | | |
215 | 28.8k | if (!proto->decode) { |
216 | 22.1k | raw_free: |
217 | 22.1k | talloc_free(vp); |
218 | 22.1k | goto raw; |
219 | 0 | } |
220 | | |
221 | 28.8k | slen = proto->decode(vp, &vp->vp_group, data, data_len); |
222 | 28.8k | if (slen < 0) goto raw_free; |
223 | | |
224 | 18.4k | vp->vp_tainted = true; |
225 | | |
226 | 18.4k | } else { |
227 | 4.22k | if (!ref) ref = fr_dict_root(dict_dhcpv6); |
228 | | |
229 | | /* |
230 | | * Child VPs go into the child group, not in the main parent list. BUT, we start |
231 | | * decoding attributes from the ref, and not from the group parent. |
232 | | */ |
233 | 4.22k | slen = fr_pair_tlvs_from_network(vp, &vp->vp_group, ref, data, data_len, decode_ctx, decode_option, NULL, false); |
234 | 4.22k | if (slen < 0) goto raw_free; |
235 | 4.22k | } |
236 | | |
237 | 19.5k | fr_pair_append(out, vp); |
238 | 19.5k | return data_len; |
239 | | |
240 | 10.0k | case FR_TYPE_IPV6_ADDR: |
241 | 10.0k | vp = fr_pair_afrom_da(ctx, parent); |
242 | 10.0k | if (!vp) return PAIR_DECODE_OOM; |
243 | 10.0k | PAIR_ALLOCED(vp); |
244 | | |
245 | 10.0k | slen = fr_value_box_ipaddr_from_network(&vp->data, parent->type, parent, |
246 | 10.0k | 128, data, data_len, |
247 | 10.0k | true, true); |
248 | 10.0k | if (slen < 0) goto raw_free; |
249 | 8.84k | break; |
250 | | |
251 | 148k | default: |
252 | 148k | vp = fr_pair_afrom_da(ctx, parent); |
253 | 148k | if (!vp) return PAIR_DECODE_OOM; |
254 | 148k | PAIR_ALLOCED(vp); |
255 | | |
256 | 148k | slen = fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da, |
257 | 148k | &FR_DBUFF_TMP(data, data_len), data_len, true); |
258 | 148k | if (slen < 0) goto raw_free; |
259 | 141k | break; |
260 | 549k | } |
261 | | |
262 | | /* |
263 | | * The input is larger than the decoded value, re-do it as a raw attribute. |
264 | | */ |
265 | 152k | if (!parent->flags.array && ((size_t) slen < data_len)) { |
266 | 0 | talloc_free(vp); |
267 | 0 | goto raw; |
268 | 0 | } |
269 | | |
270 | 152k | fr_assert(vp != NULL); |
271 | | |
272 | 152k | vp->vp_tainted = true; |
273 | 152k | fr_pair_append(out, vp); |
274 | | |
275 | 152k | if (parent->flags.array) return slen; |
276 | | |
277 | 33.4k | return data_len; |
278 | 152k | } |
279 | | |
280 | | |
281 | | static ssize_t decode_option(TALLOC_CTX *ctx, fr_pair_list_t *out, |
282 | | fr_dict_attr_t const *parent, |
283 | | uint8_t const *data, size_t const data_len, void *decode_ctx) |
284 | 89.8k | { |
285 | 89.8k | unsigned int option; |
286 | 89.8k | size_t len; |
287 | 89.8k | ssize_t slen; |
288 | 89.8k | fr_dict_attr_t const *da; |
289 | 89.8k | fr_dhcpv6_decode_ctx_t *packet_ctx = decode_ctx; |
290 | | |
291 | | #ifdef STATIC_ANALYZER |
292 | | if (!packet_ctx || !packet_ctx->tmp_ctx) return PAIR_DECODE_FATAL_ERROR; |
293 | | #endif |
294 | | |
295 | | /* |
296 | | * Must have at least an option header. |
297 | | */ |
298 | 89.8k | if (data_len < 4) { |
299 | 2.63k | fr_strerror_printf("%s: Insufficient data", __FUNCTION__); |
300 | 2.63k | return -(data_len); |
301 | 2.63k | } |
302 | | |
303 | 87.1k | option = DHCPV6_GET_OPTION_NUM(data); |
304 | 87.1k | len = DHCPV6_GET_OPTION_LEN(data); |
305 | 87.1k | if (len > (data_len - 4)) { |
306 | 4.23k | fr_strerror_printf("%s: Option overflows input. " |
307 | 4.23k | "Optional length must be less than %zu bytes, got %zu bytes", |
308 | 4.23k | __FUNCTION__, data_len - 4, len); |
309 | 4.23k | return PAIR_DECODE_FATAL_ERROR; |
310 | 4.23k | } |
311 | | |
312 | 82.9k | da = fr_dict_attr_child_by_num(parent, option); |
313 | 82.9k | if (!da) { |
314 | 15.0k | da = fr_dict_attr_unknown_raw_afrom_num(packet_ctx->tmp_ctx, parent, option); |
315 | 15.0k | if (!da) return PAIR_DECODE_FATAL_ERROR; |
316 | 15.0k | } |
317 | 82.9k | FR_PROTO_TRACE("decode context changed %s -> %s",da->parent->name, da->name); |
318 | | |
319 | | /* |
320 | | * Relay messages are weird, and contain complete DHCPv6 |
321 | | * packets, copied verbatim from the DHCPv6 client. |
322 | | */ |
323 | 82.9k | if (da == attr_relay_message) { |
324 | 5.46k | fr_pair_t *vp; |
325 | | |
326 | 5.46k | vp = fr_pair_afrom_da(ctx, attr_relay_message); |
327 | 5.46k | if (!vp) return PAIR_DECODE_FATAL_ERROR; |
328 | 5.46k | PAIR_ALLOCED(vp); |
329 | | |
330 | | /* |
331 | | * Check if the relayed packet is OK. If not, it's an error. |
332 | | */ |
333 | 5.46k | if (!fr_dhcpv6_ok(data + 4, len, 200)) return -1; |
334 | | |
335 | 2.66k | slen = fr_dhcpv6_decode(vp, &vp->vp_group, data + 4, len); |
336 | 2.66k | if (slen < 0) { |
337 | 1.39k | talloc_free(vp); |
338 | 3.97k | raw: |
339 | 3.97k | slen = fr_pair_raw_from_network(ctx, out, da, data + 4, len); |
340 | 3.97k | if (slen < 0) return slen; |
341 | 3.97k | return 4 + slen; |
342 | 3.97k | } |
343 | | |
344 | 1.27k | fr_pair_append(out, vp); |
345 | | |
346 | 77.4k | } else if ((da->type == FR_TYPE_STRING) && fr_dhcpv6_flag_any_dns_label(da)) { |
347 | 3.81k | slen = fr_pair_dns_labels_from_network(ctx, out, da, data + 4, data + 4, len, NULL, true); |
348 | 3.81k | if (slen < 0) return slen; |
349 | | |
350 | 73.6k | } else if (da->flags.array) { |
351 | 2.65k | if (da == attr_option_request) { |
352 | 907 | uint8_t const *p; |
353 | | |
354 | 907 | if ((len < 2) || ((len & 0x01) != 0)) { |
355 | 412 | fr_strerror_const("Invalid Option-Request, length is not a multiple of 2"); |
356 | 412 | return -1; |
357 | 412 | } |
358 | | |
359 | | /* |
360 | | * Check for the forbidden options. |
361 | | */ |
362 | 338k | for (p = data + 4; p < (data + 4 + len); p += 2) { |
363 | 338k | if (*p) continue; |
364 | | |
365 | 99.4k | if (*p > 26) continue; |
366 | | |
367 | 99.4k | if ((1 << *p) & DHCPV6_ORO_FORBIDDEN) { |
368 | 0 | fr_strerror_printf("Option %u is forbidden inside of Option-Request", *p); |
369 | 0 | return -1; |
370 | 0 | } |
371 | 99.4k | } |
372 | 495 | } |
373 | | |
374 | 2.24k | slen = fr_pair_array_from_network(ctx, out, da, data + 4, len, decode_ctx, decode_value); |
375 | | |
376 | 71.0k | } else if (da->type == FR_TYPE_VSA) { |
377 | 3.69k | bool append_vsa = false; |
378 | 3.69k | bool append_vp = false; |
379 | 3.69k | uint32_t pen; |
380 | 3.69k | fr_dict_attr_t const *vendor; |
381 | 3.69k | fr_pair_t *vsa, *vp; |
382 | 3.69k | fr_pair_list_t list; |
383 | | |
384 | | /* |
385 | | * Insufficient room for a 4-octet enterprise |
386 | | * code, plus at least one option header. |
387 | | */ |
388 | 3.69k | if (len < 8) goto raw; |
389 | | |
390 | 3.27k | pen = fr_nbo_to_uint32(data + 4); |
391 | | |
392 | | /* |
393 | | * Verify that the VSA contains a vendor. |
394 | | * |
395 | | * If it doesn't then this vendor is unknown, but we know |
396 | | * vendor attributes have a standard format, so we can |
397 | | * decode the data anyway. |
398 | | */ |
399 | 3.27k | vendor = fr_dict_attr_child_by_num(da, pen); |
400 | 3.27k | if (!vendor) { |
401 | 1.57k | fr_dict_attr_t *n; |
402 | | |
403 | 1.57k | n = fr_dict_attr_unknown_vendor_afrom_num(packet_ctx->tmp_ctx, da, pen); |
404 | 1.57k | if (!n) return PAIR_DECODE_OOM; |
405 | 1.57k | vendor = n; |
406 | 1.57k | } |
407 | | |
408 | 3.27k | vsa = fr_pair_find_by_da(out, NULL, da); |
409 | 3.27k | if (!vsa) { |
410 | 1.36k | vsa = fr_pair_afrom_da(ctx, da); |
411 | 1.36k | if (!vsa) return PAIR_DECODE_FATAL_ERROR; |
412 | 1.36k | PAIR_ALLOCED(vsa); |
413 | | |
414 | 1.36k | append_vsa = true; |
415 | 1.36k | } |
416 | | |
417 | 3.27k | vp = NULL; |
418 | 3.27k | if (!append_vsa) vp = fr_pair_find_by_da(&vsa->vp_group, NULL, vendor); |
419 | | |
420 | 3.27k | if (!vp) { |
421 | 2.30k | vp = fr_pair_afrom_da(vsa, vendor); |
422 | 2.30k | if (!vp) return PAIR_DECODE_FATAL_ERROR; |
423 | 2.30k | PAIR_ALLOCED(vp); |
424 | | |
425 | 2.30k | append_vp = true; |
426 | 2.30k | } |
427 | | |
428 | 3.27k | fr_pair_list_init(&list); |
429 | 3.27k | slen = fr_pair_tlvs_from_network(vp, &vp->vp_group, vendor, data + 8, len - 4, |
430 | 3.27k | decode_ctx, decode_option, NULL, false); |
431 | | |
432 | 3.27k | if (slen < 0) { |
433 | 1.80k | fr_pair_list_free(&list); |
434 | 1.80k | if (append_vp) talloc_free(vp); |
435 | 1.80k | if (append_vsa) talloc_free(vsa); |
436 | 1.80k | goto raw; |
437 | 1.80k | } |
438 | | |
439 | | /* |
440 | | * The child list contains vendors, and we have to merge the vendors |
441 | | */ |
442 | 1.47k | fr_pair_list_append(&vp->vp_group, &list); |
443 | 1.47k | if (append_vp) fr_pair_append(&vsa->vp_group, vp); |
444 | 1.47k | if (append_vsa) fr_pair_append(out, vsa); |
445 | | |
446 | 67.3k | } else if (da->type == FR_TYPE_TLV) { |
447 | 833 | slen = fr_pair_tlvs_from_network(ctx, out, da, data + 4, len, decode_ctx, decode_option, NULL, true); |
448 | | |
449 | 66.4k | } else if (da == attr_auth) { |
450 | | /* |
451 | | * See RFC 8415 Section 18 (option format), and Section 20 (functionality). |
452 | | * |
453 | | * The Auth option can be sent from a server to client in a Reply message, and contains a |
454 | | * 128-bit secret key that is recorded somewhere on the server side. |
455 | | * |
456 | | * The Auth option can be sent from a server to a client in a Reconfigure message, and |
457 | | * contains an HMAC-MD5 of the packet and the secret key. |
458 | | * |
459 | | * We are not currently a DHCPv6 client, so we do not support the Auth option, or |
460 | | * Reconfigure messages. See verify_to_client() in base.c. |
461 | | */ |
462 | 244 | fr_strerror_const("The 'Auth' option is not supported"); |
463 | 244 | return -1; |
464 | | |
465 | 66.2k | } else { |
466 | 66.2k | slen = decode_value(ctx, out, da, data + 4, len, decode_ctx); |
467 | 66.2k | } |
468 | | |
469 | 75.8k | if (slen < 0) goto raw; |
470 | | |
471 | 75.5k | return len + 4; |
472 | 75.8k | } |
473 | | |
474 | | |
475 | | /** Create a "normal" fr_pair_t from the given data |
476 | | * |
477 | | * 0 1 2 3 |
478 | | * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
479 | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
480 | | * | option-code | option-len | |
481 | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
482 | | */ |
483 | | ssize_t fr_dhcpv6_decode_option(TALLOC_CTX *ctx, fr_pair_list_t *out, |
484 | | uint8_t const *data, size_t data_len, void *decode_ctx) |
485 | 72.0k | { |
486 | 72.0k | FR_PROTO_HEX_DUMP(data, data_len, "fr_dhcpv6_decode_pair"); |
487 | | |
488 | | /* |
489 | | * The API changes, so we just bounce directly to the |
490 | | * decode_option() function. |
491 | | * |
492 | | * All options including VSAs in DHCPv6 MUST follow the |
493 | | * standard format. |
494 | | */ |
495 | 72.0k | return decode_option(ctx, out, fr_dict_root(dict_dhcpv6), data, data_len, decode_ctx); |
496 | 72.0k | } |
497 | | |
498 | | ssize_t fr_dhcpv6_decode_foreign(TALLOC_CTX *ctx, fr_pair_list_t *out, |
499 | | uint8_t const *data, size_t data_len) |
500 | 4.52k | { |
501 | 4.52k | ssize_t slen; |
502 | 4.52k | uint8_t const *attr, *end; |
503 | | |
504 | 4.52k | fr_dhcpv6_decode_ctx_t decode_ctx = {}; |
505 | | |
506 | 4.52k | fr_assert(dict_dhcpv6 != NULL); |
507 | | |
508 | 4.52k | decode_ctx.tmp_ctx = talloc(ctx, uint8_t); |
509 | | |
510 | 4.52k | attr = data; |
511 | 4.52k | end = data + data_len; |
512 | | |
513 | 19.4k | while (attr < end) { |
514 | 18.3k | slen = fr_dhcpv6_decode_option(ctx, out, attr, (end - attr), &decode_ctx); |
515 | 18.3k | if (slen < 0) { |
516 | 3.46k | talloc_free(decode_ctx.tmp_ctx); |
517 | 3.46k | return slen; |
518 | 3.46k | } |
519 | | |
520 | | /* |
521 | | * If slen is larger than the room in the packet, |
522 | | * all kinds of bad things happen. |
523 | | */ |
524 | 14.9k | if (!fr_cond_assert(slen <= (end - attr))) { |
525 | 0 | talloc_free(decode_ctx.tmp_ctx); |
526 | 0 | return -slen - (attr - data); |
527 | 0 | } |
528 | | |
529 | 14.9k | attr += slen; |
530 | 14.9k | talloc_free_children(decode_ctx.tmp_ctx); |
531 | 14.9k | } |
532 | | |
533 | 4.52k | talloc_free(decode_ctx.tmp_ctx); |
534 | 1.06k | return data_len; |
535 | 4.52k | } |
536 | | |
537 | | static int decode_test_ctx(void **out, TALLOC_CTX *ctx, UNUSED fr_dict_t const *dict, |
538 | | UNUSED fr_dict_attr_t const *root_da) |
539 | 6.63k | { |
540 | 6.63k | fr_dhcpv6_decode_ctx_t *test_ctx; |
541 | | |
542 | 6.63k | test_ctx = talloc_zero(ctx, fr_dhcpv6_decode_ctx_t); |
543 | 6.63k | if (!test_ctx) return -1; |
544 | | |
545 | 6.63k | test_ctx->tmp_ctx = talloc(test_ctx, uint8_t); |
546 | | |
547 | 6.63k | *out = test_ctx; |
548 | | |
549 | 6.63k | return 0; |
550 | 6.63k | } |
551 | | |
552 | | static ssize_t fr_dhcpv6_decode_proto(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *data, size_t data_len, UNUSED void *proto_ctx) |
553 | 5.68k | { |
554 | 5.68k | size_t packet_len = data_len; |
555 | | // fr_dhcpv6_decode_ctx_t *test_ctx = talloc_get_type_abort(proto_ctx, fr_dhcpv6_decode_ctx_t); |
556 | | |
557 | 5.68k | if (!fr_dhcpv6_ok(data, packet_len, 200)) return -1; |
558 | | |
559 | 5.57k | return fr_dhcpv6_decode(ctx, out, data, packet_len); |
560 | 5.68k | } |
561 | | |
562 | | |
563 | | static ssize_t decode_pair(TALLOC_CTX *ctx, fr_pair_list_t *out, NDEBUG_UNUSED fr_dict_attr_t const *parent, |
564 | | uint8_t const *data, size_t data_len, void *decode_ctx) |
565 | 0 | { |
566 | 0 | fr_assert(parent == fr_dict_root(dict_dhcpv6)); |
567 | |
|
568 | 0 | return decode_option(ctx, out, fr_dict_root(dict_dhcpv6), data, data_len, decode_ctx); |
569 | 0 | } |
570 | | |
571 | | /* |
572 | | * Test points |
573 | | */ |
574 | | extern fr_test_point_pair_decode_t dhcpv6_tp_decode_pair; |
575 | | fr_test_point_pair_decode_t dhcpv6_tp_decode_pair = { |
576 | | .test_ctx = decode_test_ctx, |
577 | | .func = decode_pair, |
578 | | }; |
579 | | |
580 | | extern fr_test_point_proto_decode_t dhcpv6_tp_decode_proto; |
581 | | fr_test_point_proto_decode_t dhcpv6_tp_decode_proto = { |
582 | | .test_ctx = decode_test_ctx, |
583 | | .func = fr_dhcpv6_decode_proto |
584 | | }; |