The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
encode.c
Go to the documentation of this file.
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: 9ebb29c3c94c49f0955f6e997b546841adaf70ae $
19 *
20 * @file protocols/dhcpv6/encode.c
21 * @brief Functions to encode 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#include <freeradius-devel/io/test_point.h>
31#include <freeradius-devel/util/dbuff.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
39static ssize_t encode_value(fr_dbuff_t *dbuff,
40 fr_da_stack_t *da_stack, unsigned int depth,
41 fr_dcursor_t *cursor, void *encode_ctx);
42
43static ssize_t encode_rfc(fr_dbuff_t *dbuff,
44 fr_da_stack_t *da_stack, unsigned int depth,
45 fr_dcursor_t *cursor, void *encode_ctx);
46
47static ssize_t encode_tlv(fr_dbuff_t *dbuff,
48 fr_da_stack_t *da_stack, unsigned int depth,
49 fr_dcursor_t *cursor, void *encode_ctx);
50
51static ssize_t encode_child(fr_dbuff_t *dbuff,
52 fr_da_stack_t *da_stack, unsigned int depth,
53 fr_dcursor_t *cursor, void *encode_ctx);
54
55/** Macro-like function for encoding an option header
56 *
57 * 0 1 2 3
58 * 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
59 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
60 * | option-code | option-len |
61 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
62 *
63 * @param[out] m Where to write the 4 byte option header.
64 * @param[in] option The option number (host byte order).
65 * @param[in] data_len The length of the option (host byte order).
66 * @return
67 * - <0 How much data would have been required as a negative value.
68 * - 4 The length of data written.
69 */
70static inline ssize_t encode_option_hdr(fr_dbuff_marker_t *m, uint16_t option, size_t data_len)
71{
72 FR_DBUFF_IN_RETURN(m, option);
73 FR_DBUFF_IN_RETURN(m, (uint16_t) data_len);
74
75 return sizeof(option) + sizeof(uint16_t);
76}
77
78
80 fr_da_stack_t *da_stack, unsigned int depth,
81 fr_dcursor_t *cursor, void *encode_ctx)
82{
83 ssize_t slen;
84 fr_dbuff_t work_dbuff = FR_DBUFF(dbuff);
85 fr_pair_t const *vp = fr_dcursor_current(cursor);
86 fr_dict_attr_t const *da = da_stack->da[depth];
87
89 FR_PROTO_STACK_PRINT(da_stack, depth);
90
91 /*
92 * Pack multiple attributes into into a single option
93 */
94 if ((vp->vp_type == FR_TYPE_STRUCT) || (da->type == FR_TYPE_STRUCT)) {
95 slen = fr_struct_to_network(&work_dbuff, da_stack, depth, cursor, encode_ctx, encode_value, encode_child);
96 if (slen <= 0) return slen;
97
98 vp = fr_dcursor_current(cursor);
99 fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL);
100 return fr_dbuff_set(dbuff, &work_dbuff);
101 }
102
103 /*
104 * If it's not a TLV, it should be a value type RFC
105 * attribute make sure that it is.
106 */
107 if (da_stack->da[depth + 1] != NULL) {
108 fr_strerror_printf("%s: Encoding value but not at top of stack", __FUNCTION__);
110 }
111
112 if ((vp->da != da) && !vp->da->flags.is_raw) {
113 fr_strerror_printf("%s: Top of stack %s does not match encoding attribute %s",
114 __FUNCTION__, da->name, vp->da->name);
116 }
117
118 switch (vp->vp_type) {
119 case FR_TYPE_ATTR:
120 /*
121 * Internal attributes are numbered from 65536, and are not options.
122 */
123 if (vp->vp_attr->attr > UINT16_MAX) {
124 fr_strerror_printf("%s: Attribute %s is not a DHCPv6 option", __FUNCTION__, vp->vp_attr->name);
126 }
127 FR_DBUFF_IN_RETURN(&work_dbuff, (uint16_t) vp->vp_attr->attr);
128 break;
129
130 case FR_TYPE_TLV:
131 case FR_TYPE_VENDOR:
132 case FR_TYPE_VSA:
133 fr_strerror_printf("%s: Called with structural type %s", __FUNCTION__,
134 fr_type_to_str(da->type));
136
137 /*
138 * 0 1 2 3
139 * 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
140 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
141 * | option-code | option-len |
142 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
143 * . String .
144 * | ... |
145 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
146 */
147 case FR_TYPE_STRING:
148 /*
149 * DNS labels get a special encoder. DNS labels
150 * MUST NOT be compressed in DHCP.
151 *
152 * https://tools.ietf.org/html/rfc8415#section-10
153 */
155 fr_dbuff_marker_t last_byte, src;
156
157 fr_dbuff_marker(&last_byte, &work_dbuff);
158 fr_dbuff_marker(&src, &work_dbuff);
159 slen = fr_dns_label_from_value_box_dbuff(&work_dbuff, false, &vp->data, NULL);
160 if (slen < 0) return slen;
161
162 /*
163 * RFC 4704 says "FQDN", unless it's a
164 * single label, in which case it's a
165 * partial name, and we omit the trailing
166 * zero.
167 */
168 if (fr_dhcpv6_flag_partial_dns_label(da) && slen > 0) {
169 uint8_t c = 0;
170
171 fr_dbuff_advance(&last_byte, (size_t)(slen - 1));
172 fr_dbuff_set(&src, &last_byte);
173 fr_dbuff_out(&c, &src);
174 if (!c) fr_dbuff_set(&work_dbuff, &last_byte);
175 }
176 break;
177 }
178 goto to_network;
179
180 /*
181 * Common encoder might add scope byte, so we just copy the address portion
182 *
183 * 0 1 2 3
184 * 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
185 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
186 * | option-code | option-len |
187 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
188 * | |
189 * | ipv6-address |
190 * | |
191 * | |
192 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
193 */
195 FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, vp->vp_ipv6addr, sizeof(vp->vp_ipv6addr));
196 break;
197
198 /*
199 * Common encoder doesn't add a reserved byte after prefix, but it also
200 * doesn't do the variable length encoding required.
201 *
202 * 0 1 2 3
203 * 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
204 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
205 * | option-code | option-length |
206 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
207 * | prefix6len | ipv6-prefix |
208 * +-+-+-+-+-+-+-+-+ (variable length) |
209 * . .
210 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
211 */
213 {
214 size_t prefix_len;
215
216 /*
217 * Structs have fixed length value fields.
218 */
219 if (da->parent->type == FR_TYPE_STRUCT) {
220 prefix_len = sizeof(vp->vp_ipv6addr);
221 } else {
222 prefix_len = fr_bytes_from_bits(vp->vp_ip.prefix);
223 }
224
225 FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, vp->vp_ip.prefix);
226 FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, (uint8_t const *)&vp->vp_ipv6addr, prefix_len); /* Only copy the minimum address bytes required */
227 }
228 break;
229
230 /*
231 * Not actually specified by the DHCPv6 RFC, but will probably come
232 * in handy at some point if we need to have the DHCPv6 server
233 * hand out v4 prefixes.
234 */
236 {
237 size_t prefix_len;
238
239 /*
240 * Structs have fixed length value fields.
241 */
242 if (da->parent->type == FR_TYPE_STRUCT) {
243 prefix_len = sizeof(vp->vp_ipv4addr);
244 } else {
245 prefix_len = fr_bytes_from_bits(vp->vp_ip.prefix);
246 }
247
248 FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, vp->vp_ip.prefix);
249 FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, (uint8_t const *)&vp->vp_ipv4addr, prefix_len); /* Only copy the minimum address bytes required */
250 }
251 break;
252
253 /*
254 * 0 1 2 3
255 * 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
256 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
257 * | option-code | option-len |
258 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
259 */
260 case FR_TYPE_BOOL:
261 /*
262 * Don't encode anything! The mere existence of
263 * the attribute signifies a "true" value.
264 */
265 break;
266
267 /*
268 * A standard 32bit integer, but unlike normal UNIX timestamps
269 * starts from the 1st of January 2000.
270 *
271 * In the decoder we add 30 years to any values, so here
272 * we need to subtract that time, or if the value is less
273 * than that time, just encode a 0x00000000
274 * value.
275 */
276 case FR_TYPE_DATE:
277 {
278 uint64_t date = fr_unix_time_to_sec(vp->vp_date);
279
280 if (date < DHCPV6_DATE_OFFSET) { /* 30 years */
281 FR_DBUFF_IN_RETURN(&work_dbuff, (uint32_t) 0);
282 break;
283 }
284
285 FR_DBUFF_IN_RETURN(&work_dbuff, (uint32_t)(date - DHCPV6_DATE_OFFSET));
286 }
287 break;
288
289 case FR_TYPE_GROUP:
290 {
291 fr_dcursor_t child_cursor;
292 fr_dict_attr_t const *ref = fr_dict_attr_ref(vp->da);
293
294 if (ref && (ref->dict != dict_dhcpv6)) {
295 slen = fr_pair_ref_to_network(&work_dbuff, da_stack, depth, cursor);
296 if (slen < 0) return PAIR_ENCODE_FATAL_ERROR;
297 break;
298 }
299
300 /*
301 * Encode the child options.
302 */
303 if (!fr_pair_list_empty(&vp->vp_group)) {
304 (void) fr_pair_dcursor_child_iter_init(&child_cursor, &vp->vp_group, cursor);
305
306 /*
307 * @todo - encode from "ref" and not from the root? But that's hard,
308 * due to the whole proto stack thing, which we largely don't need
309 * any more.
310 */
311 while (fr_dcursor_current(&child_cursor) != NULL) {
312 slen = fr_dhcpv6_encode_option(&work_dbuff, &child_cursor, encode_ctx);
313
314 if (slen < 0) return PAIR_ENCODE_FATAL_ERROR;
315 }
316 }
317 }
318 break;
319
320 /*
321 * The value_box functions will take care of fixed-width
322 * "string" and "octets" options.
323 */
324 to_network:
325 case FR_TYPE_OCTETS:
326 /*
327 * Hack until we find all places that don't set data.enumv
328 */
329 if (vp->da->flags.length && (vp->data.enumv != vp->da)) {
330 fr_dict_attr_t const * const *c = &vp->data.enumv;
331 fr_dict_attr_t **u;
332
333 memcpy(&u, &c, sizeof(c)); /* const issues */
334 memcpy(u, &vp->da, sizeof(vp->da));
335 }
337
338 /*
339 * 0 1 2 3
340 * 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
341 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
342 * | option-code | option-len |
343 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
344 * | 8-bit-integer |
345 * +-+-+-+-+-+-+-+-+
346 */
347 case FR_TYPE_UINT8:
348
349 /*
350 * 0 1 2 3
351 * 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
352 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
353 * | option-code | option-len |
354 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
355 * | 16-bit-integer |
356 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
357 */
358 case FR_TYPE_UINT16:
359 /*
360 * 0 1 2 3
361 * 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
362 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
363 * | option-code | option-len |
364 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
365 * | 32-bit-integer |
366 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
367 */
368 case FR_TYPE_UINT32:
369 default:
370 slen = fr_value_box_to_network(&work_dbuff, &vp->data);
371 if (slen < 0) return PAIR_ENCODE_FATAL_ERROR;
372 break;
373 }
374
375 /*
376 * Rebuilds the TLV stack for encoding the next attribute
377 */
378 vp = fr_dcursor_next(cursor);
379 fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL);
380
381 return fr_dbuff_set(dbuff, &work_dbuff);
382}
383
384
385static ssize_t encode_vsio(fr_dbuff_t *dbuff,
386 fr_da_stack_t *da_stack, unsigned int depth,
387 fr_dcursor_t *cursor, void *encode_ctx);
388
390 fr_da_stack_t *da_stack, unsigned int depth,
391 fr_dcursor_t *cursor, void *encode_ctx)
392{
393 ssize_t len;
395 fr_dcursor_t child_cursor;
396 fr_dbuff_t work_dbuff;
397
398 if (da_stack->da[depth]) {
399 /*
400 * Determine the nested type and call the appropriate encoder
401 */
402 switch (da_stack->da[depth]->type) {
403 case FR_TYPE_TLV:
404 if (!da_stack->da[depth + 1]) break;
405
406 return encode_tlv(dbuff, da_stack, depth, cursor, encode_ctx);
407
408 case FR_TYPE_VSA:
409 if (!da_stack->da[depth + 1]) break;
410
411 return encode_vsio(dbuff, da_stack, depth, cursor, encode_ctx);
412
413 case FR_TYPE_GROUP:
414 if (!da_stack->da[depth + 1]) break;
416
417 default:
418 return encode_rfc(dbuff, da_stack, depth, cursor, encode_ctx);
419 }
420 }
421
423
424 fr_pair_dcursor_child_iter_init(&child_cursor, &vp->vp_group, cursor);
425 work_dbuff = FR_DBUFF(dbuff);
426
427 while ((vp = fr_dcursor_current(&child_cursor)) != NULL) {
428 fr_proto_da_stack_build(da_stack, vp->da);
429
430 switch (da_stack->da[depth]->type) {
431 case FR_TYPE_VSA:
432 len = encode_vsio(&work_dbuff, da_stack, depth, &child_cursor, encode_ctx);
433 break;
434
435 case FR_TYPE_TLV:
436 len = encode_tlv(&work_dbuff, da_stack, depth, &child_cursor, encode_ctx);
437 break;
438
439 default:
440 len = encode_rfc(&work_dbuff, da_stack, depth, &child_cursor, encode_ctx);
441 break;
442 }
443
444 if (len <= 0) return len;
445 }
446
447 /*
448 * Skip over the attribute we just encoded.
449 */
450 vp = fr_dcursor_next(cursor);
451 fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL);
452
453 return fr_dbuff_set(dbuff, &work_dbuff);
454}
455
456/** Encode an RFC format TLV.
457 *
458 * This could be a standard attribute, or a TLV data type.
459 * If it's a standard attribute, then vp->da->attr == attribute.
460 * Otherwise, attribute may be something else.
461 */
463 fr_da_stack_t *da_stack, unsigned int depth,
464 fr_dcursor_t *cursor, void *encode_ctx)
465{
466 fr_dbuff_t work_dbuff = FR_DBUFF(dbuff);
468 fr_dict_attr_t const *da = da_stack->da[depth];
469 ssize_t slen;
470
471 FR_PROTO_STACK_PRINT(da_stack, depth);
472 fr_dbuff_marker(&hdr, &work_dbuff);
473
474 /*
475 * Make space for the header...
476 */
478
479 /*
480 * Write out the option's value
481 */
482 if (da->flags.array) {
483 slen = fr_pair_array_to_network(&work_dbuff, da_stack, depth, cursor, encode_ctx, encode_value);
484 } else {
485 slen = encode_value(&work_dbuff, da_stack, depth, cursor, encode_ctx);
486 }
487 if (slen < 0) return slen;
488
489 /*
490 * Write out the option number and length (before the value we just wrote)
491 */
492 (void) encode_option_hdr(&hdr, (uint16_t)da->attr, (uint16_t) (fr_dbuff_used(&work_dbuff) - DHCPV6_OPT_HDR_LEN));
493
494 FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), "Done RFC header");
495
496 return fr_dbuff_set(dbuff, &work_dbuff);
497}
498
500 fr_da_stack_t *da_stack, unsigned int depth,
501 fr_dcursor_t *cursor, void *encode_ctx)
502{
503 fr_dbuff_t work_dbuff = FR_DBUFF(dbuff);
505 fr_dict_attr_t const *da = da_stack->da[depth];
506 ssize_t len;
507
508 fr_dbuff_marker(&hdr, &work_dbuff);
510 FR_PROTO_STACK_PRINT(da_stack, depth);
511
512 if (da_stack->da[depth]->type != FR_TYPE_TLV) {
513 fr_strerror_printf("%s: Expected type \"tlv\" got \"%s\"", __FUNCTION__,
514 fr_type_to_str(da_stack->da[depth]->type));
516 }
517
518 if (!da_stack->da[depth + 1]) {
519 fr_strerror_printf("%s: Can't encode empty TLV", __FUNCTION__);
521 }
522
523 FR_DBUFF_ADVANCE_EXTEND_RETURN(&work_dbuff, DHCPV6_OPT_HDR_LEN); /* Make room for option header */
524
525 len = fr_pair_cursor_to_network(&work_dbuff, da_stack, depth, cursor, encode_ctx, encode_child);
526 if (len < 0) return len;
527
528 /*
529 * 0 1 2 3
530 * 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
531 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
532 * | option-code | option-len |
533 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
534 */
535 (void) encode_option_hdr(&hdr, (uint16_t)da->attr, (uint16_t) (fr_dbuff_used(&work_dbuff) - DHCPV6_OPT_HDR_LEN));
536
537 FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), "Done TLV header");
538
539 return fr_dbuff_set(dbuff, &work_dbuff);
540}
541
542/** Encode a Vendor-Specific Information Option
543 *
544 * 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
545 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
546 * | OPTION_VENDOR_OPTS | option-len |
547 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
548 * | enterprise-number |
549 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
550 * . .
551 * . option-data .
552 * . .
553 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
554 */
556 fr_da_stack_t *da_stack, unsigned int depth,
557 fr_dcursor_t *cursor, void *encode_ctx)
558{
559 fr_dbuff_t work_dbuff = FR_DBUFF(dbuff);
561 fr_dict_attr_t const *da = da_stack->da[depth];
562 fr_dict_attr_t const *dv;
563 ssize_t len;
564
565 fr_dbuff_marker(&hdr, &work_dbuff);
566 FR_PROTO_STACK_PRINT(da_stack, depth);
567
568 /*
569 * DA should be a VSA type with the value of OPTION_VENDOR_OPTS.
570 */
571 if (da->type != FR_TYPE_VSA) {
572 fr_strerror_printf("%s: Expected type \"vsa\" got \"%s\"", __FUNCTION__,
573 fr_type_to_str(da->type));
575 }
576
577 /*
578 * Now process the vendor ID part (which is one attribute deeper)
579 */
580 dv = da_stack->da[++depth];
581 FR_PROTO_STACK_PRINT(da_stack, depth);
582
583 if (dv->type != FR_TYPE_VENDOR) {
584 fr_strerror_printf("%s: Expected type \"vsa\" got \"%s\"", __FUNCTION__,
585 fr_type_to_str(dv->type));
587 }
588
590 FR_DBUFF_IN_RETURN(&work_dbuff, dv->attr);
591
592 /*
593 * https://tools.ietf.org/html/rfc8415#section-21.17 says:
594 *
595 * The vendor-option-data field MUST be encoded as a sequence of
596 * code/length/value fields of format identical to the DHCP options (see
597 * Section 21.1). The sub-option codes are defined by the vendor
598 * identified in the enterprise-number field and are not managed by
599 * IANA. Each of the sub-options is formatted as follows:
600 *
601 * 0 1 2 3
602 * 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
603 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
604 * | sub-opt-code | sub-option-len |
605 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
606 * . .
607 * . sub-option-data .
608 * . .
609 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
610 */
611
612 /*
613 * Encode the different data types
614 */
615 len = encode_child(&work_dbuff, da_stack, depth + 1, cursor, encode_ctx);
616 if (len < 0) return len;
617
618 (void) encode_option_hdr(&hdr, da->attr, fr_dbuff_used(&work_dbuff) - DHCPV6_OPT_HDR_LEN);
619
620 FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), "Done VSIO header");
621
622 return fr_dbuff_set(dbuff, &work_dbuff);
623}
624
625/** Encode a Relay-Message
626 *
627 * Header + stuff
628 */
630 fr_da_stack_t *da_stack, unsigned int depth,
631 fr_dcursor_t *cursor, UNUSED void *encode_ctx)
632{
633 fr_dbuff_t work_dbuff = FR_DBUFF(dbuff);
634 fr_dbuff_marker_t len_m;
635 ssize_t slen;
636
637 fr_dict_attr_t const *da = da_stack->da[depth];
638 fr_pair_t *vp;
639
640 FR_PROTO_STACK_PRINT(da_stack, depth);
641
642 /*
643 * Skip empty relay messages...
644 * This shouldn't really happen.
645 */
646 vp = fr_dcursor_current(cursor);
647 if (fr_pair_list_empty(&vp->vp_group)) {
648 vp = fr_dcursor_next(cursor);
649 fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL);
650 return 0;
651 }
652
653 /*
654 * Write out the header
655 */
656 FR_DBUFF_IN_RETURN(&work_dbuff, (uint16_t)da->attr); /* Write out the option header */
657 fr_dbuff_marker(&len_m, &work_dbuff); /* Mark where we'll need to put the length field */
658 FR_DBUFF_ADVANCE_EXTEND_RETURN(&work_dbuff, 2); /* Advanced past the length field */
659
660 vp = fr_dcursor_current(cursor);
661 slen = fr_dhcpv6_encode(&work_dbuff, NULL, 0, 0, &vp->vp_group);
662 if (slen <= 0) return slen;
663
664 fr_dbuff_in(&len_m, (uint16_t)slen); /* Write out the length value */
665
666 FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), "Done Relay-Message header");
667
668 vp = fr_dcursor_next(cursor);
669 fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL);
670
671 return fr_dbuff_set(dbuff, &work_dbuff);
672}
673
674/** Encode a DHCPv6 option and any sub-options.
675 *
676 * @param[out] dbuff Where to write encoded DHCP attributes.
677 * @param[in] cursor with current VP set to the option to be encoded.
678 * Will be advanced to the next option to encode.
679 * @param[in] encode_ctx containing parameters for the encoder.
680 * @return
681 * - > 0 length of data written.
682 * - < 0 error.
683 */
685{
686 fr_pair_t *vp;
687 unsigned int depth = 0;
688 fr_da_stack_t da_stack;
689 fr_dbuff_t work_dbuff = FR_DBUFF_MAX(dbuff, DHCPV6_OPT_HDR_LEN + UINT16_MAX);
690 ssize_t slen;
691
692 vp = fr_dcursor_current(cursor);
693 if (!vp) return 0;
694
695 FR_PROTO_TRACE("encoding option %s", vp->da->name);
696
697 if (vp->da->flags.internal) {
698 fr_strerror_printf("Attribute \"%s\" is not a DHCPv6 option", vp->da->name);
699 fr_dcursor_next(cursor);
700 return 0;
701 }
702
703 fr_proto_da_stack_build(&da_stack, vp->da);
704
705 FR_PROTO_STACK_PRINT(&da_stack, depth);
706
707 /*
708 * Deal with nested options
709 */
710 switch (da_stack.da[depth]->type) {
711 case FR_TYPE_GROUP:
712 /*
713 * Relay-Message has a special format, it's an entire packet. :(
714 */
715 if (da_stack.da[depth] == attr_relay_message) {
716 slen = encode_relay_message(&work_dbuff, &da_stack, depth, cursor, encode_ctx);
717 break;
718 }
719
720 slen = encode_rfc(&work_dbuff, &da_stack, depth, cursor, encode_ctx);
721 break;
722
723 case FR_TYPE_STRUCT:
724 /*
725 * See RFC 8415 Section 18 (option format), and Section 20 (functionality).
726 *
727 * The Auth option can be sent from a server to client in a Reply message, and contains a
728 * 128-bit secret key that is recorded somewhere on the server side.
729 *
730 * The Auth option can be sent from a server to a client in a Reconfigure message, and
731 * contains an HMAC-MD5 of the packet and the secret key.
732 *
733 * We are not currently a DHCPv6 client, so we do not support the Auth option, or
734 * Reconfigure messages. See verify_to_client() in base.c.
735 */
736 if (da_stack.da[depth] == attr_auth) {
737 fr_strerror_const("The 'Auth' option is not supported");
738 return -1;
739 }
741
742 default:
743 slen = encode_child(&work_dbuff, &da_stack, depth, cursor, encode_ctx);
744 break;
745 }
746 if (slen < 0) return slen;
747
748 FR_PROTO_TRACE("Complete option is %zu byte(s)", fr_dbuff_used(&work_dbuff));
749 FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), NULL);
750
751 return fr_dbuff_set(dbuff, &work_dbuff);
752}
753
755{
756 ssize_t slen;
757 fr_dcursor_t cursor;
758 fr_dbuff_t work_dbuff = FR_DBUFF(dbuff);
759
760 fr_assert(dict_dhcpv6 != NULL);
761
763
764 while (fr_dcursor_current(&cursor) != NULL) {
765 slen = fr_dhcpv6_encode_option(&work_dbuff, &cursor, &(fr_dhcpv6_encode_ctx_t){ .root = fr_dict_root(dict_dhcpv6) });
766 if (slen < 0) return slen;
767 }
768
769 FR_PROTO_TRACE("Foreign option is %zu byte(s)", fr_dbuff_used(&work_dbuff));
770 FR_PROTO_HEX_DUMP(dbuff->p, fr_dbuff_used(&work_dbuff), NULL);
771
772 return fr_dbuff_set(dbuff, &work_dbuff);
773}
774
775
776static int encode_test_ctx(void **out, TALLOC_CTX *ctx, UNUSED fr_dict_t const *dict,
778{
779 fr_dhcpv6_encode_ctx_t *test_ctx;
780
781 test_ctx = talloc_zero(ctx, fr_dhcpv6_encode_ctx_t);
782 if (!test_ctx) return -1;
783
784 test_ctx->root = fr_dict_root(dict_dhcpv6);
785
786 *out = test_ctx;
787
788 return 0;
789}
790
791static ssize_t fr_dhcpv6_encode_proto(UNUSED TALLOC_CTX *ctx, fr_pair_list_t *vps, uint8_t *data, size_t data_len, UNUSED void *proto_ctx)
792{
793 ssize_t slen;
794
795 slen = fr_dhcpv6_encode(&FR_DBUFF_TMP(data, data_len), NULL, 0, 0, vps);
796
797#ifndef NDEBUG
798 if (slen <= 0) return slen;
799
800 if (fr_debug_lvl > 2) {
801 fr_dhcpv6_print_hex(stdout, data, slen);
802 }
803#endif
804
805 return slen;
806}
807
808/*
809 * Test points
810 */
817
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition build.h:422
#define UNUSED
Definition build.h:415
fr_dict_attr_t const * root_da
Definition common.c:32
#define fr_dbuff_advance(_dbuff_or_marker, _len)
Advance 'current' position in dbuff or marker by _len bytes.
Definition dbuff.h:1121
#define fr_dbuff_used(_dbuff_or_marker)
Return the number of bytes remaining between the start of the dbuff or marker and the current positio...
Definition dbuff.h:819
#define FR_DBUFF_ADVANCE_EXTEND_RETURN(_dbuff_or_marker, _len)
Advance the 'current' position in dbuff or marker by _len bytes (extending if necessary) returning if...
Definition dbuff.h:1203
struct fr_dbuff_marker_s fr_dbuff_marker_t
A position marker associated with a dbuff.
Definition dbuff.h:81
#define fr_dbuff_set(_dst, _src)
Set the 'current' position in a dbuff or marker using another dbuff or marker, a char pointer,...
Definition dbuff.h:1056
#define fr_dbuff_start(_dbuff_or_marker)
Return the 'start' position of a dbuff or marker.
Definition dbuff.h:950
static uint8_t * fr_dbuff_marker(fr_dbuff_marker_t *m, fr_dbuff_t *dbuff)
Initialises a new marker pointing to the 'current' position of the dbuff.
Definition dbuff.h:1245
#define FR_DBUFF_IN_MEMCPY_RETURN(_dbuff_or_marker, _in, _inlen)
Copy exactly _inlen bytes into dbuff or marker returning if there's insufficient space.
Definition dbuff.h:1435
#define fr_dbuff_in(_dbuff_or_marker, _in)
Copy data from a fixed sized C type into a dbuff or marker.
Definition dbuff.h:1620
#define FR_DBUFF_IN_RETURN(_dbuff_or_marker, _in)
Copy data from a fixed sized C type into a dbuff returning if there is insufficient space.
Definition dbuff.h:1638
#define FR_DBUFF(_dbuff_or_marker)
Create a new dbuff pointing to the same underlying buffer.
Definition dbuff.h:230
#define FR_DBUFF_MAX(_dbuff_or_marker, _max)
Limit the maximum number of bytes available in the dbuff when passing it to another function.
Definition dbuff.h:334
#define fr_dbuff_out(_out, _dbuff_or_marker)
Copy data from a dbuff or marker to a fixed sized C type.
Definition dbuff.h:1852
#define FR_DBUFF_IN_BYTES_RETURN(_dbuff_or_marker,...)
Copy a byte sequence into a dbuff or marker returning if there's insufficient space.
Definition dbuff.h:1525
#define FR_DBUFF_TMP(_start, _len_or_end)
Creates a compound literal to pass into functions which accept a dbuff.
Definition dbuff.h:556
static void * fr_dcursor_next(fr_dcursor_t *cursor)
Advanced the cursor to the next item.
Definition dcursor.h:288
static void * fr_dcursor_current(fr_dcursor_t *cursor)
Return the item the cursor current points to.
Definition dcursor.h:337
Implementation of the DHCPv6 protocol.
fr_dict_attr_t const * root
Root attribute of the dictionary.
Definition dhcpv6.h:127
static bool fr_dhcpv6_flag_partial_dns_label(fr_dict_attr_t const *da)
Definition dhcpv6.h:175
static bool fr_dhcpv6_flag_any_dns_label(fr_dict_attr_t const *da)
Definition dhcpv6.h:180
#define DHCPV6_DATE_OFFSET
Definition dhcpv6.h:124
#define DHCPV6_OPT_HDR_LEN
Definition dhcpv6.h:45
fr_dict_attr_t const * fr_dict_root(fr_dict_t const *dict)
Return the root attribute of a dictionary.
Definition dict_util.c:2717
static fr_dict_attr_t const * fr_dict_attr_ref(fr_dict_attr_t const *da)
Return the reference associated with a group type attribute.
Definition dict_ext.h:149
ssize_t fr_dns_label_from_value_box_dbuff(fr_dbuff_t *dbuff, bool compression, fr_value_box_t const *value, fr_dns_labels_t *lb)
Encode a single value box of type string, serializing its contents to a dns label in a dbuff.
Definition dns.c:603
#define PAIR_ENCODE_FATAL_ERROR
Fatal encoding error.
Definition pair.h:36
static ssize_t encode_value(fr_dbuff_t *dbuff, fr_da_stack_t *da_stack, int depth, fr_dcursor_t *cursor, void *encode_ctx)
Encodes the data portion of an attribute.
Definition encode.c:276
static ssize_t encode_tlv(fr_dbuff_t *dbuff, fr_da_stack_t *da_stack, unsigned int depth, fr_dcursor_t *cursor, void *encode_ctx)
Definition encode.c:744
static ssize_t encode_rfc(fr_dbuff_t *dbuff, fr_da_stack_t *da_stack, unsigned int depth, fr_dcursor_t *cursor, void *encode_ctx)
Encode an RFC format attribute header.
Definition encode.c:597
ssize_t fr_pair_array_to_network(fr_dbuff_t *dbuff, fr_da_stack_t *da_stack, int depth, fr_dcursor_t *cursor, void *encode_ctx, fr_encode_dbuff_t encode_value)
Encode an array of values from the network.
Definition encode.c:41
ssize_t fr_pair_ref_to_network(fr_dbuff_t *dbuff, fr_da_stack_t *da_stack, unsigned int depth, fr_dcursor_t *cursor)
Encode a foreign reference to the network.
Definition encode.c:117
ssize_t fr_pair_cursor_to_network(fr_dbuff_t *dbuff, fr_da_stack_t *da_stack, unsigned int depth, fr_dcursor_t *cursor, void *encode_ctx, fr_encode_dbuff_t encode_pair)
Definition encode.c:73
int fr_debug_lvl
Definition log.c:41
unsigned short uint16_t
@ FR_TYPE_TLV
Contains nested attributes.
@ FR_TYPE_IPV6_PREFIX
IPv6 Prefix.
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_UINT16
16 Bit unsigned integer.
@ FR_TYPE_DATE
Unix time stamp, always has value >2^31.
@ FR_TYPE_UINT8
8 Bit unsigned integer.
@ FR_TYPE_UINT32
32 Bit unsigned integer.
@ FR_TYPE_STRUCT
like TLV, but without T or L, and fixed-width children
@ FR_TYPE_VENDOR
Attribute that represents a vendor in the attribute tree.
@ FR_TYPE_IPV6_ADDR
128 Bit IPv6 Address.
@ FR_TYPE_IPV4_PREFIX
IPv4 Prefix.
@ FR_TYPE_BOOL
A truth value.
@ FR_TYPE_VSA
Vendor-Specific, for RADIUS attribute 26.
@ FR_TYPE_OCTETS
Raw octets.
@ FR_TYPE_GROUP
A grouping of other attributes.
unsigned int uint32_t
long int ssize_t
unsigned char uint8_t
static uint8_t depth(fr_minmax_heap_index_t i)
Definition minmax_heap.c:83
static unsigned int fr_bytes_from_bits(unsigned int bits)
Convert bits (as in prefix length) to bytes, rounding up.
Definition nbo.h:243
void fr_proto_da_stack_build(fr_da_stack_t *stack, fr_dict_attr_t const *da)
Build a complete DA stack from the da back to the root.
Definition proto.c:118
static fr_dict_t const * dict_dhcpv6
static fr_dict_attr_t const * attr_relay_message
static fr_internal_encode_ctx_t encode_ctx
static int encode_test_ctx(void **out, TALLOC_CTX *ctx, UNUSED fr_dict_t const *dict, UNUSED fr_dict_attr_t const *root_da)
Definition encode.c:165
static ssize_t encode_child(fr_dbuff_t *dbuff, fr_da_stack_t *da_stack, unsigned int depth, fr_dcursor_t *cursor, void *encode_ctx)
Definition encode.c:364
HIDDEN fr_dict_attr_t const * attr_auth
Definition base.c:56
void * fr_dhcpv6_next_encodable(fr_dcursor_t *cursor, void *current, void *uctx)
DHCPV6-specific iterator.
Definition base.c:759
void fr_dhcpv6_print_hex(FILE *fp, uint8_t const *packet, size_t packet_len)
Print a raw DHCP packet as hex.
Definition base.c:952
ssize_t fr_dhcpv6_encode(fr_dbuff_t *dbuff, uint8_t const *original, size_t length, int msg_type, fr_pair_list_t *vps)
Encode a DHCPv6 packet.
Definition base.c:778
fr_test_point_pair_encode_t dhcpv6_tp_encode_pair
Definition encode.c:812
static ssize_t fr_dhcpv6_encode_proto(UNUSED TALLOC_CTX *ctx, fr_pair_list_t *vps, uint8_t *data, size_t data_len, UNUSED void *proto_ctx)
Definition encode.c:791
static ssize_t encode_relay_message(fr_dbuff_t *dbuff, fr_da_stack_t *da_stack, unsigned int depth, fr_dcursor_t *cursor, UNUSED void *encode_ctx)
Encode a Relay-Message.
Definition encode.c:629
static ssize_t encode_option_hdr(fr_dbuff_marker_t *m, uint16_t option, size_t data_len)
Macro-like function for encoding an option header.
Definition encode.c:70
ssize_t fr_dhcpv6_encode_option(fr_dbuff_t *dbuff, fr_dcursor_t *cursor, void *encode_ctx)
Encode a DHCPv6 option and any sub-options.
Definition encode.c:684
fr_test_point_proto_encode_t dhcpv6_tp_encode_proto
Definition encode.c:819
ssize_t fr_dhcpv6_encode_foreign(fr_dbuff_t *dbuff, fr_pair_list_t const *list)
Definition encode.c:754
static ssize_t encode_vsio(fr_dbuff_t *dbuff, fr_da_stack_t *da_stack, unsigned int depth, fr_dcursor_t *cursor, void *encode_ctx)
Encode a Vendor-Specific Information Option.
Definition encode.c:555
VQP attributes.
#define fr_assert(_expr)
Definition rad_assert.h:37
fr_pair_t * vp
ssize_t fr_struct_to_network(fr_dbuff_t *dbuff, fr_da_stack_t *da_stack, unsigned int depth, fr_dcursor_t *parent_cursor, void *encode_ctx, fr_encode_dbuff_t encode_value, fr_encode_dbuff_t encode_pair)
Definition struct.c:750
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
fr_dict_attr_t const *_CONST da
Dictionary attribute defines the attribute number, vendor and type of the pair.
Definition pair.h:69
fr_test_point_ctx_alloc_t test_ctx
Allocate a test ctx for the encoder.
Definition test_point.h:94
fr_test_point_ctx_alloc_t test_ctx
Allocate a test ctx for the encoder.
Definition test_point.h:76
Entry point for pair encoders.
Definition test_point.h:93
Entry point for protocol encoders.
Definition test_point.h:75
static int64_t fr_unix_time_to_sec(fr_unix_time_t delta)
Definition time.h:506
#define fr_pair_dcursor_iter_init(_cursor, _list, _iter, _uctx)
Initialises a special dcursor with callbacks that will maintain the attr sublists correctly.
Definition pair.h:585
static fr_pair_t * fr_pair_dcursor_child_iter_init(fr_dcursor_t *cursor, fr_pair_list_t const *list, fr_dcursor_t const *parent)
Initializes a child dcursor from a parent cursor, with an iteration function.
Definition pair.h:623
bool fr_pair_list_empty(fr_pair_list_t const *list)
Is a valuepair list empty.
#define PAIR_VERIFY(_x)
Definition pair.h:209
#define FR_PROTO_HEX_DUMP(_data, _data_len, _fmt,...)
Definition proto.h:42
#define FR_PROTO_TRACE(_fmt,...)
Definition proto.h:41
#define FR_PROTO_STACK_PRINT(_stack, _depth)
Definition proto.h:44
fr_dict_attr_t const * da[FR_DICT_MAX_TLV_STACK+1]
The stack.
Definition proto.h:57
Structure for holding the stack of dictionary attributes being encoded.
Definition proto.h:55
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition strerror.h:64
#define fr_strerror_const(_msg)
Definition strerror.h:223
@ FR_TYPE_ATTR
A contains an attribute reference.
Definition types.h:83
#define fr_type_is_structural(_x)
Definition types.h:392
static char const * fr_type_to_str(fr_type_t type)
Return a static string containing the type name.
Definition types.h:454
ssize_t fr_value_box_to_network(fr_dbuff_t *dbuff, fr_value_box_t const *value)
Encode a single value box, serializing its contents in generic network format.
Definition value.c:1534
static fr_slen_t data
Definition value.h:1366
static fr_sbuff_err_t char ** out
Definition value.h:1061
static fr_sbuff_err_t char size_t * len
Definition value.h:1061