Coverage Report

Created: 2026-05-11 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/protocols/dhcpv6/encode.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: 3b9692e41494cb514d1f6521de68ff9659be45da $
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
39
static 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
43
static 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
47
static 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
51
static 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
 */
70
static inline ssize_t encode_option_hdr(fr_dbuff_marker_t *m, uint16_t option, size_t data_len)
71
0
{
72
0
  FR_DBUFF_IN_RETURN(m, option);
73
0
  FR_DBUFF_IN_RETURN(m, (uint16_t) data_len);
74
75
0
  return sizeof(option) + sizeof(uint16_t);
76
0
}
77
78
79
static ssize_t encode_value(fr_dbuff_t *dbuff,
80
          fr_da_stack_t *da_stack, unsigned int depth,
81
          fr_dcursor_t *cursor, void *encode_ctx)
82
0
{
83
0
  ssize_t     slen;
84
0
  fr_dbuff_t    work_dbuff = FR_DBUFF(dbuff);
85
0
  fr_pair_t const   *vp = fr_dcursor_current(cursor);
86
0
  fr_dict_attr_t const  *da = da_stack->da[depth];
87
88
0
  PAIR_VERIFY(vp);
89
0
  FR_PROTO_STACK_PRINT(da_stack, depth);
90
91
  /*
92
   *  Pack multiple attributes into into a single option
93
   */
94
0
  if ((vp->vp_type == FR_TYPE_STRUCT) || (da->type == FR_TYPE_STRUCT)) {
95
0
    slen = fr_struct_to_network(&work_dbuff, da_stack, depth, cursor, encode_ctx, encode_value, encode_child);
96
0
    if (slen <= 0) return slen;
97
98
0
    vp = fr_dcursor_current(cursor);
99
0
    fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL);
100
0
    return fr_dbuff_set(dbuff, &work_dbuff);
101
0
  }
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
0
  if (da_stack->da[depth + 1] != NULL) {
108
0
    fr_strerror_printf("%s: Encoding value but not at top of stack", __FUNCTION__);
109
0
    return PAIR_ENCODE_FATAL_ERROR;
110
0
  }
111
112
0
  if (vp->da != da) {
113
0
    fr_strerror_printf("%s: Top of stack %s does not match encoding attribute %s",
114
0
           __FUNCTION__, da->name, vp->da->name);
115
0
    return PAIR_ENCODE_FATAL_ERROR;
116
0
  }
117
118
0
  switch (vp->vp_type) {
119
0
  case FR_TYPE_ATTR:
120
0
    FR_DBUFF_IN_RETURN(&work_dbuff, (uint16_t) vp->vp_attr->attr);
121
0
    break;
122
123
0
  case FR_TYPE_TLV:
124
0
  case FR_TYPE_VENDOR:
125
0
  case FR_TYPE_VSA:
126
0
    fr_strerror_printf("%s: Called with structural type %s", __FUNCTION__,
127
0
           fr_type_to_str(da->type));
128
0
    return PAIR_ENCODE_FATAL_ERROR;
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
0
  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
     */
147
0
    if (fr_dhcpv6_flag_any_dns_label(da)) {
148
0
      fr_dbuff_marker_t last_byte, src;
149
150
0
      fr_dbuff_marker(&last_byte, &work_dbuff);
151
0
      fr_dbuff_marker(&src, &work_dbuff);
152
0
      slen = fr_dns_label_from_value_box_dbuff(&work_dbuff, false, &vp->data, NULL);
153
0
      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
0
      if (fr_dhcpv6_flag_partial_dns_label(da) && slen > 0) {
162
0
        uint8_t c = 0;
163
164
0
        fr_dbuff_advance(&last_byte, (size_t)(slen - 1));
165
0
        fr_dbuff_set(&src, &last_byte);
166
0
        fr_dbuff_out(&c, &src);
167
0
        if (!c) fr_dbuff_set(&work_dbuff, &last_byte);
168
0
      }
169
0
      break;
170
0
    }
171
0
    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
   */
187
0
  case FR_TYPE_IPV6_ADDR:
188
0
    FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, vp->vp_ipv6addr, sizeof(vp->vp_ipv6addr));
189
0
    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
   */
205
0
  case FR_TYPE_IPV6_PREFIX:
206
0
  {
207
0
    size_t  prefix_len;
208
209
    /*
210
     *  Structs have fixed length value fields.
211
     */
212
0
    if (da->parent->type == FR_TYPE_STRUCT) {
213
0
      prefix_len = sizeof(vp->vp_ipv6addr);
214
0
    } else {
215
0
      prefix_len = fr_bytes_from_bits(vp->vp_ip.prefix);
216
0
    }
217
218
0
    FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, vp->vp_ip.prefix);
219
0
    FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, (uint8_t const *)&vp->vp_ipv6addr, prefix_len); /* Only copy the minimum address bytes required */
220
0
  }
221
0
    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
   */
228
0
  case FR_TYPE_IPV4_PREFIX:
229
0
  {
230
0
    size_t prefix_len;
231
232
    /*
233
     *  Structs have fixed length value fields.
234
     */
235
0
    if (da->parent->type == FR_TYPE_STRUCT) {
236
0
      prefix_len = sizeof(vp->vp_ipv4addr);
237
0
    } else {
238
0
      prefix_len = fr_bytes_from_bits(vp->vp_ip.prefix);
239
0
    }
240
241
0
    FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, vp->vp_ip.prefix);
242
0
    FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, (uint8_t const *)&vp->vp_ipv4addr, prefix_len); /* Only copy the minimum address bytes required */
243
0
  }
244
0
    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
0
  case FR_TYPE_BOOL:
254
    /*
255
     *  Don't encode anything!  The mere existence of
256
     *  the attribute signifies a "true" value.
257
     */
258
0
    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 0x00000000
267
   *  value.
268
   */
269
0
  case FR_TYPE_DATE:
270
0
  {
271
0
    uint64_t date = fr_unix_time_to_sec(vp->vp_date);
272
273
0
    if (date < DHCPV6_DATE_OFFSET) { /* 30 years */
274
0
      FR_DBUFF_IN_RETURN(&work_dbuff, (uint32_t) 0);
275
0
      break;
276
0
    }
277
278
0
    FR_DBUFF_IN_RETURN(&work_dbuff, (uint32_t)(date - DHCPV6_DATE_OFFSET));
279
0
  }
280
0
    break;
281
282
0
  case FR_TYPE_GROUP:
283
0
  {
284
0
    fr_dcursor_t child_cursor;
285
0
    fr_dict_attr_t const *ref = fr_dict_attr_ref(vp->da);
286
287
0
    if (ref && (ref->dict != dict_dhcpv6)) {
288
0
      slen = fr_pair_ref_to_network(&work_dbuff, da_stack, depth, cursor);
289
0
      if (slen < 0) return PAIR_ENCODE_FATAL_ERROR;
290
0
      break;
291
0
    }
292
293
    /*
294
     *  Encode the child options.
295
     */
296
0
    if (!fr_pair_list_empty(&vp->vp_group)) {
297
0
      (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
0
      while (fr_dcursor_current(&child_cursor) != NULL) {
305
0
        slen = fr_dhcpv6_encode_option(&work_dbuff, &child_cursor, encode_ctx);
306
307
0
        if (slen < 0) return PAIR_ENCODE_FATAL_ERROR;
308
0
      }
309
0
    }
310
0
  }
311
0
    break;
312
313
  /*
314
   *  The value_box functions will take care of fixed-width
315
   *  "string" and "octets" options.
316
   */
317
0
  to_network:
318
0
  case FR_TYPE_OCTETS:
319
    /*
320
     *  Hack until we find all places that don't set data.enumv
321
     */
322
0
    if (vp->da->flags.length && (vp->data.enumv != vp->da)) {
323
0
      fr_dict_attr_t const * const *c = &vp->data.enumv;
324
0
      fr_dict_attr_t **u;
325
326
0
      memcpy(&u, &c, sizeof(c)); /* const issues */
327
0
      memcpy(u, &vp->da, sizeof(vp->da));
328
0
    }
329
0
    FALL_THROUGH;
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
0
  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
0
  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
0
  case FR_TYPE_UINT32:
362
0
  default:
363
0
    slen = fr_value_box_to_network(&work_dbuff, &vp->data);
364
0
    if (slen < 0) return PAIR_ENCODE_FATAL_ERROR;
365
0
    break;
366
0
  }
367
368
  /*
369
   *  Rebuilds the TLV stack for encoding the next attribute
370
   */
371
0
  vp = fr_dcursor_next(cursor);
372
0
  fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL);
373
374
0
  return fr_dbuff_set(dbuff, &work_dbuff);
375
0
}
376
377
378
static 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
382
static ssize_t encode_child(fr_dbuff_t *dbuff,
383
          fr_da_stack_t *da_stack, unsigned int depth,
384
          fr_dcursor_t *cursor, void *encode_ctx)
385
0
{
386
0
  ssize_t len;
387
0
  fr_pair_t *vp = fr_dcursor_current(cursor);
388
0
  fr_dcursor_t child_cursor;
389
0
  fr_dbuff_t work_dbuff;
390
391
0
  if (da_stack->da[depth]) {
392
    /*
393
     *  Determine the nested type and call the appropriate encoder
394
     */
395
0
    switch (da_stack->da[depth]->type) {
396
0
    case FR_TYPE_TLV:
397
0
      if (!da_stack->da[depth + 1]) break;
398
399
0
      return encode_tlv(dbuff, da_stack, depth, cursor, encode_ctx);
400
401
0
    case FR_TYPE_VSA:
402
0
      if (!da_stack->da[depth + 1]) break;
403
404
0
      return encode_vsio(dbuff, da_stack, depth, cursor, encode_ctx);
405
406
0
    case FR_TYPE_GROUP:
407
0
      if (!da_stack->da[depth + 1]) break;
408
0
      FALL_THROUGH;
409
410
0
    default:
411
0
      return encode_rfc(dbuff, da_stack, depth, cursor, encode_ctx);
412
0
    }
413
0
  }
414
415
0
  fr_assert(fr_type_is_structural(vp->vp_type));
416
417
0
  fr_pair_dcursor_child_iter_init(&child_cursor, &vp->vp_group, cursor);
418
0
  work_dbuff = FR_DBUFF(dbuff);
419
420
0
  while ((vp = fr_dcursor_current(&child_cursor)) != NULL) {
421
0
    fr_proto_da_stack_build(da_stack, vp->da);
422
423
0
    switch (da_stack->da[depth]->type) {
424
0
    case FR_TYPE_VSA:
425
0
      len = encode_vsio(&work_dbuff, da_stack, depth, &child_cursor, encode_ctx);
426
0
      break;
427
428
0
    case FR_TYPE_TLV:
429
0
      len = encode_tlv(&work_dbuff, da_stack, depth, &child_cursor, encode_ctx);
430
0
      break;
431
432
0
    default:
433
0
      len = encode_rfc(&work_dbuff, da_stack, depth, &child_cursor, encode_ctx);
434
0
      break;
435
0
    }
436
437
0
    if (len <= 0) return len;
438
0
  }
439
440
  /*
441
   *  Skip over the attribute we just encoded.
442
   */
443
0
  vp = fr_dcursor_next(cursor);
444
0
  fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL);
445
446
0
  return fr_dbuff_set(dbuff, &work_dbuff);
447
0
}
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
 */
455
static ssize_t encode_rfc(fr_dbuff_t *dbuff,
456
            fr_da_stack_t *da_stack, unsigned int depth,
457
            fr_dcursor_t *cursor, void *encode_ctx)
458
0
{
459
0
  fr_dbuff_t    work_dbuff = FR_DBUFF(dbuff);
460
0
  fr_dbuff_marker_t hdr;
461
0
  fr_dict_attr_t const  *da = da_stack->da[depth];
462
0
  ssize_t     slen;
463
464
0
  FR_PROTO_STACK_PRINT(da_stack, depth);
465
0
  fr_dbuff_marker(&hdr, &work_dbuff);
466
467
  /*
468
   *  Make space for the header...
469
   */
470
0
  FR_DBUFF_EXTEND_LOWAT_OR_RETURN(&work_dbuff, DHCPV6_OPT_HDR_LEN);
471
0
  fr_dbuff_advance(&work_dbuff, DHCPV6_OPT_HDR_LEN);
472
473
  /*
474
   *  Write out the option's value
475
   */
476
0
  if (da->flags.array) {
477
0
    slen = fr_pair_array_to_network(&work_dbuff, da_stack, depth, cursor, encode_ctx, encode_value);
478
0
  } else {
479
0
    slen = encode_value(&work_dbuff, da_stack, depth, cursor, encode_ctx);
480
0
  }
481
0
  if (slen < 0) return slen;
482
483
  /*
484
   *  Write out the option number and length (before the value we just wrote)
485
   */
486
0
  (void) encode_option_hdr(&hdr, (uint16_t)da->attr, (uint16_t) (fr_dbuff_used(&work_dbuff) - DHCPV6_OPT_HDR_LEN));
487
488
0
  FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), "Done RFC header");
489
490
0
  return fr_dbuff_set(dbuff, &work_dbuff);
491
0
}
492
493
static ssize_t encode_tlv(fr_dbuff_t *dbuff,
494
            fr_da_stack_t *da_stack, unsigned int depth,
495
            fr_dcursor_t *cursor, void *encode_ctx)
496
0
{
497
0
  fr_dbuff_t    work_dbuff = FR_DBUFF(dbuff);
498
0
  fr_dbuff_marker_t hdr;
499
0
  fr_dict_attr_t const  *da = da_stack->da[depth];
500
0
  ssize_t     len;
501
502
0
  fr_dbuff_marker(&hdr, &work_dbuff);
503
0
  PAIR_VERIFY(fr_dcursor_current(cursor));
504
0
  FR_PROTO_STACK_PRINT(da_stack, depth);
505
506
0
  if (da_stack->da[depth]->type != FR_TYPE_TLV) {
507
0
    fr_strerror_printf("%s: Expected type \"tlv\" got \"%s\"", __FUNCTION__,
508
0
           fr_type_to_str(da_stack->da[depth]->type));
509
0
    return PAIR_ENCODE_FATAL_ERROR;
510
0
  }
511
512
0
  if (!da_stack->da[depth + 1]) {
513
0
    fr_strerror_printf("%s: Can't encode empty TLV", __FUNCTION__);
514
0
    return PAIR_ENCODE_FATAL_ERROR;
515
0
  }
516
517
0
  FR_DBUFF_ADVANCE_RETURN(&work_dbuff, DHCPV6_OPT_HDR_LEN); /* Make room for option header */
518
519
0
  len = fr_pair_cursor_to_network(&work_dbuff, da_stack, depth, cursor, encode_ctx, encode_child);
520
0
  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
0
  (void) encode_option_hdr(&hdr, (uint16_t)da->attr, (uint16_t) (fr_dbuff_used(&work_dbuff) - DHCPV6_OPT_HDR_LEN));
530
531
0
  FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), "Done TLV header");
532
533
0
  return fr_dbuff_set(dbuff, &work_dbuff);
534
0
}
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
 */
549
static ssize_t encode_vsio(fr_dbuff_t *dbuff,
550
             fr_da_stack_t *da_stack, unsigned int depth,
551
             fr_dcursor_t *cursor, void *encode_ctx)
552
0
{
553
0
  fr_dbuff_t    work_dbuff = FR_DBUFF(dbuff);
554
0
  fr_dbuff_marker_t hdr;
555
0
  fr_dict_attr_t const  *da = da_stack->da[depth];
556
0
  fr_dict_attr_t const  *dv;
557
0
  ssize_t     len;
558
559
0
  fr_dbuff_marker(&hdr, &work_dbuff);
560
0
  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
0
  if (da->type != FR_TYPE_VSA) {
566
0
    fr_strerror_printf("%s: Expected type \"vsa\" got \"%s\"", __FUNCTION__,
567
0
           fr_type_to_str(da->type));
568
0
    return PAIR_ENCODE_FATAL_ERROR;
569
0
  }
570
571
  /*
572
   *  Now process the vendor ID part (which is one attribute deeper)
573
   */
574
0
  dv = da_stack->da[++depth];
575
0
  FR_PROTO_STACK_PRINT(da_stack, depth);
576
577
0
  if (dv->type != FR_TYPE_VENDOR) {
578
0
    fr_strerror_printf("%s: Expected type \"vsa\" got \"%s\"", __FUNCTION__,
579
0
           fr_type_to_str(dv->type));
580
0
    return PAIR_ENCODE_FATAL_ERROR;
581
0
  }
582
583
0
  FR_DBUFF_EXTEND_LOWAT_OR_RETURN(&work_dbuff, DHCPV6_OPT_HDR_LEN);
584
0
  fr_dbuff_advance(&work_dbuff, DHCPV6_OPT_HDR_LEN);
585
0
  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
0
  len = encode_child(&work_dbuff, da_stack, depth + 1, cursor, encode_ctx);
611
0
  if (len < 0) return len;
612
613
0
  (void) encode_option_hdr(&hdr, da->attr, fr_dbuff_used(&work_dbuff) - DHCPV6_OPT_HDR_LEN);
614
615
0
  FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), "Done VSIO header");
616
617
0
  return fr_dbuff_set(dbuff, &work_dbuff);
618
0
}
619
620
/** Encode a Relay-Message
621
 *
622
 *  Header + stuff
623
 */
624
static ssize_t encode_relay_message(fr_dbuff_t *dbuff,
625
            fr_da_stack_t *da_stack, unsigned int depth,
626
            fr_dcursor_t *cursor, UNUSED void *encode_ctx)
627
0
{
628
0
  fr_dbuff_t    work_dbuff = FR_DBUFF(dbuff);
629
0
  fr_dbuff_marker_t len_m;
630
0
  ssize_t     slen;
631
632
0
  fr_dict_attr_t const  *da = da_stack->da[depth];
633
0
  fr_pair_t   *vp;
634
635
0
  FR_PROTO_STACK_PRINT(da_stack, depth);
636
637
  /*
638
   *  Skip empty relay messages...
639
   *  This shouldn't really happen.
640
   */
641
0
  vp = fr_dcursor_current(cursor);
642
0
  if (fr_pair_list_empty(&vp->vp_group)) {
643
0
    vp = fr_dcursor_next(cursor);
644
0
    fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL);
645
0
    return 0;
646
0
  }
647
648
  /*
649
   *  Write out the header
650
   */
651
0
  FR_DBUFF_IN_RETURN(&work_dbuff, (uint16_t)da->attr); /* Write out the option header */
652
0
  fr_dbuff_marker(&len_m, &work_dbuff);     /* Mark where we'll need to put the length field */
653
0
  FR_DBUFF_ADVANCE_RETURN(&work_dbuff, 2);   /* Advanced past the length field */
654
655
0
  vp = fr_dcursor_current(cursor);
656
0
  slen = fr_dhcpv6_encode(&work_dbuff, NULL, 0, 0, &vp->vp_group);
657
0
  if (slen <= 0) return slen;
658
659
0
  fr_dbuff_in(&len_m, (uint16_t)slen);   /* Write out the length value */
660
661
0
  FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), "Done Relay-Message header");
662
663
0
  vp = fr_dcursor_next(cursor);
664
0
  fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL);
665
666
0
  return fr_dbuff_set(dbuff, &work_dbuff);
667
0
}
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
 */
679
ssize_t fr_dhcpv6_encode_option(fr_dbuff_t *dbuff, fr_dcursor_t *cursor, void * encode_ctx)
680
0
{
681
0
  fr_pair_t   *vp;
682
0
  unsigned int    depth = 0;
683
0
  fr_da_stack_t   da_stack;
684
0
  fr_dbuff_t    work_dbuff = FR_DBUFF_MAX(dbuff, DHCPV6_OPT_HDR_LEN + UINT16_MAX);
685
0
  ssize_t     slen;
686
687
0
  vp = fr_dcursor_current(cursor);
688
0
  if (!vp) return 0;
689
690
0
  FR_PROTO_TRACE("encoding option %s", vp->da->name);
691
692
0
  if (vp->da->flags.internal) {
693
0
    fr_strerror_printf("Attribute \"%s\" is not a DHCPv6 option", vp->da->name);
694
0
    fr_dcursor_next(cursor);
695
0
    return 0;
696
0
  }
697
698
0
  fr_proto_da_stack_build(&da_stack, vp->da);
699
700
0
  FR_PROTO_STACK_PRINT(&da_stack, depth);
701
702
  /*
703
   *  Deal with nested options
704
   */
705
0
  switch (da_stack.da[depth]->type) {
706
0
  case FR_TYPE_GROUP:
707
    /*
708
     *  Relay-Message has a special format, it's an entire packet. :(
709
     */
710
0
    if (da_stack.da[depth] == attr_relay_message) {
711
0
      slen = encode_relay_message(&work_dbuff, &da_stack, depth, cursor, encode_ctx);
712
0
      break;
713
0
    }
714
715
0
    slen = encode_rfc(&work_dbuff, &da_stack, depth, cursor, encode_ctx);
716
0
    break;
717
718
0
  default:
719
0
    slen = encode_child(&work_dbuff, &da_stack, depth, cursor, encode_ctx);
720
0
    break;
721
0
  }
722
0
  if (slen < 0) return slen;
723
724
0
  FR_PROTO_TRACE("Complete option is %zu byte(s)", fr_dbuff_used(&work_dbuff));
725
0
  FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), NULL);
726
727
0
  return fr_dbuff_set(dbuff, &work_dbuff);
728
0
}
729
730
ssize_t fr_dhcpv6_encode_foreign(fr_dbuff_t *dbuff, fr_pair_list_t const *list)
731
0
{
732
0
  ssize_t   slen;
733
0
  fr_dcursor_t  cursor;
734
0
  fr_dbuff_t  work_dbuff = FR_DBUFF(dbuff);
735
736
0
  fr_assert(dict_dhcpv6 != NULL);
737
738
0
  fr_pair_dcursor_iter_init(&cursor, list, fr_dhcpv6_next_encodable, dict_dhcpv6);
739
740
0
  while (fr_dcursor_current(&cursor) != NULL) {
741
0
    slen = fr_dhcpv6_encode_option(&work_dbuff, &cursor, &(fr_dhcpv6_encode_ctx_t){ .root = fr_dict_root(dict_dhcpv6) });
742
0
    if (slen < 0) return slen;
743
0
  }
744
745
0
  FR_PROTO_TRACE("Foreign option is %zu byte(s)", fr_dbuff_used(&work_dbuff));
746
0
  FR_PROTO_HEX_DUMP(dbuff->p, fr_dbuff_used(&work_dbuff), NULL);
747
748
0
  return fr_dbuff_set(dbuff, &work_dbuff);
749
0
}
750
751
752
static int encode_test_ctx(void **out, TALLOC_CTX *ctx, UNUSED fr_dict_t const *dict,
753
         UNUSED fr_dict_attr_t const *root_da)
754
8.49k
{
755
8.49k
  fr_dhcpv6_encode_ctx_t  *test_ctx;
756
757
8.49k
  test_ctx = talloc_zero(ctx, fr_dhcpv6_encode_ctx_t);
758
8.49k
  if (!test_ctx) return -1;
759
760
8.49k
  test_ctx->root = fr_dict_root(dict_dhcpv6);
761
762
8.49k
  *out = test_ctx;
763
764
8.49k
  return 0;
765
8.49k
}
766
767
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)
768
0
{
769
0
  ssize_t slen;
770
771
0
  slen = fr_dhcpv6_encode(&FR_DBUFF_TMP(data, data_len), NULL, 0, 0, vps);
772
773
0
#ifndef NDEBUG
774
0
  if (slen <= 0) return slen;
775
776
0
  if (fr_debug_lvl > 2) {
777
0
    fr_dhcpv6_print_hex(stdout, data, slen);
778
0
  }
779
0
#endif
780
781
0
  return slen;
782
0
}
783
784
/*
785
 *  Test points
786
 */
787
extern fr_test_point_pair_encode_t dhcpv6_tp_encode_pair;
788
fr_test_point_pair_encode_t dhcpv6_tp_encode_pair = {
789
  .test_ctx = encode_test_ctx,
790
  .func   = fr_dhcpv6_encode_option,
791
  .next_encodable = fr_dhcpv6_next_encodable,
792
};
793
794
extern fr_test_point_proto_encode_t dhcpv6_tp_encode_proto;
795
fr_test_point_proto_encode_t dhcpv6_tp_encode_proto = {
796
  .test_ctx = encode_test_ctx,
797
  .func   = fr_dhcpv6_encode_proto
798
};