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