Coverage Report

Created: 2025-12-31 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/protocols/dhcpv4/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: c5a8342c3115a0fb06e1e9100aef7498b17511a4 $
19
 *
20
 * @file protocols/dhcpv4/encode.c
21
 * @brief Functions to encode DHCP options.
22
 *
23
 * @copyright 2008,2017 The FreeRADIUS server project
24
 * @copyright 2008 Alan DeKok (aland@deployingradius.com)
25
 * @copyright 2015,2017 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
26
 */
27
#include <freeradius-devel/io/test_point.h>
28
#include <freeradius-devel/util/dbuff.h>
29
#include <freeradius-devel/util/proto.h>
30
#include <freeradius-devel/util/struct.h>
31
#include <freeradius-devel/util/dns.h>
32
#include <freeradius-devel/util/encode.h>
33
34
#include "dhcpv4.h"
35
#include "attrs.h"
36
37
static ssize_t encode_value(fr_dbuff_t *dbuff,
38
          fr_da_stack_t *da_stack, unsigned int depth,
39
          fr_dcursor_t *cursor, void *encode_ctx);
40
41
static ssize_t encode_child(fr_dbuff_t *dbuff,
42
          fr_da_stack_t *da_stack, unsigned int depth,
43
          fr_dcursor_t *cursor, void *encode_ctx);
44
45
/** Write DHCP option value into buffer
46
 *
47
 * Does not include DHCP option length or number.
48
 *
49
 * @param[out] dbuff    buffer to write the option to.
50
 * @param[in] da_stack    Describing nesting of options.
51
 * @param[in] depth   in da_stack.
52
 * @param[in,out] cursor  Current attribute we're encoding.
53
 * @param[in] encode_ctx  Containing DHCPv4 dictionary.
54
 * @return
55
 *  - The length of data written, may return 0 for bools
56
 *  < 0 if there's not enough space or option type is unsupported
57
 */
58
static ssize_t encode_value(fr_dbuff_t *dbuff,
59
          fr_da_stack_t *da_stack, unsigned int depth,
60
          fr_dcursor_t *cursor, void *encode_ctx)
61
0
{
62
0
  fr_pair_t *vp = fr_dcursor_current(cursor);
63
0
  fr_dbuff_t  work_dbuff = FR_DBUFF(dbuff);
64
0
  fr_dict_attr_t const  *da = da_stack->da[depth];
65
0
  ssize_t   slen;
66
67
68
0
  FR_PROTO_STACK_PRINT(da_stack, depth);
69
0
  FR_PROTO_TRACE("%zu byte(s) available for value", fr_dbuff_remaining(dbuff));
70
71
  /*
72
   *  Structures are special.
73
   */
74
0
  if ((vp->vp_type == FR_TYPE_STRUCT) || (da->type == FR_TYPE_STRUCT)) {
75
0
    slen = fr_struct_to_network(&work_dbuff, da_stack, depth, cursor, encode_ctx, encode_value, encode_child);
76
0
    if (slen <= 0) return slen;
77
78
    /*
79
     *  Rebuild the da_stack for the next option.
80
     */
81
0
    vp = fr_dcursor_current(cursor);
82
0
    fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL);
83
0
    return fr_dbuff_set(dbuff, &work_dbuff);
84
0
  }
85
86
0
  switch (da_stack->da[depth]->type) {
87
0
  case FR_TYPE_ATTR:
88
0
    FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, (uint8_t) vp->vp_attr->attr);
89
0
    break;
90
91
0
  case FR_TYPE_IPV6_PREFIX:
92
0
    FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, vp->vp_ip.prefix);
93
0
    FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, (uint8_t const *)&vp->vp_ipv6addr, sizeof(vp->vp_ipv6addr));
94
0
    break;
95
96
0
  case FR_TYPE_IPV6_ADDR:
97
0
    FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, (uint8_t const *)&vp->vp_ipv6addr, sizeof(vp->vp_ipv6addr));
98
0
    break;
99
100
    /*
101
     *  "option exists" == true.
102
     *  "option does not exist" == false
103
     *
104
     *  fr_dhcpv4_next_encodable() takes care of skipping bools which are false.
105
     *
106
     *  Rapid-Commit does this.  Options 19/20 require encoding as one byte of 0/1.
107
     */
108
0
  case FR_TYPE_BOOL:
109
0
    if (fr_dhcpv4_flag_exists(vp->da)) {
110
0
      break;
111
0
    }
112
0
    FR_DBUFF_IN_RETURN(&work_dbuff, (uint8_t) (vp->vp_bool == true));
113
0
    break;
114
115
0
  case FR_TYPE_IPV4_PREFIX:
116
0
    if (fr_dhcpv4_flag_prefix_split(vp->da)) {
117
0
      uint32_t mask;
118
119
0
      mask = ~((~(uint32_t) 0) >> vp->vp_ip.prefix);
120
121
0
      FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff,
122
0
              (uint8_t const *)&vp->vp_ipv4addr,
123
0
              sizeof(vp->vp_ipv4addr));
124
0
      FR_DBUFF_IN_RETURN(&work_dbuff, mask);
125
0
      break;
126
0
    }
127
128
0
    if (fr_dhcpv4_flag_prefix_bits(vp->da)) {
129
0
      size_t num_bytes = (vp->vp_ip.prefix + 0x07) >> 3;
130
131
0
      FR_DBUFF_IN_RETURN(&work_dbuff, (uint8_t) vp->vp_ip.prefix);
132
133
0
      if (num_bytes) {
134
0
        FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff,
135
0
                (uint8_t const *)&vp->vp_ipv4addr,
136
0
                num_bytes);
137
0
      }
138
139
0
      break;
140
0
    }
141
142
0
    goto from_network;
143
144
0
  case FR_TYPE_STRING:
145
    /*
146
     *  DNS labels get a special encoder.  DNS labels
147
     *  MUST NOT be compressed in DHCP.
148
     *
149
     *  https://tools.ietf.org/html/rfc8415#section-10
150
     */
151
0
    if (fr_dhcpv4_flag_dns_label(da)) {
152
0
      fr_dbuff_marker_t last_byte, src;
153
154
0
      fr_dbuff_marker(&last_byte, &work_dbuff);
155
0
      fr_dbuff_marker(&src, &work_dbuff);
156
0
      slen = fr_dns_label_from_value_box_dbuff(&work_dbuff, false, &vp->data, NULL);
157
0
      if (slen < 0) return slen;
158
0
      break;
159
0
    }
160
0
    FALL_THROUGH;
161
162
0
  default:
163
0
  from_network:
164
0
    slen = fr_value_box_to_network(&work_dbuff, &vp->data);
165
0
    if (slen < 0) return slen;
166
0
    break;
167
0
  }
168
169
0
  vp = fr_dcursor_next(cursor); /* We encoded a leaf, advance the cursor */
170
0
  fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL);
171
172
0
  FR_PROTO_STACK_PRINT(da_stack, depth);
173
0
  FR_PROTO_HEX_DUMP(dbuff->p, fr_dbuff_used(&work_dbuff), "Value");
174
175
0
  return fr_dbuff_set(dbuff, &work_dbuff);
176
0
}
177
178
179
/** Extend an encoded option in-place.
180
 *
181
 * @param[in] dbuff buffer containing the option
182
 * @param[in] hdr marker (with dbuff as parent) set to where the option starts
183
 * @param[in] len        length of the data being written
184
 * @return
185
 *  - <0 if we can't extend the option
186
 *  - >0  if we can, with hdr set to where the next option should start
187
 * @note  The option starts with a two-byte (type, length) header, where
188
 *    the length does *not* include the two bytes for the header.
189
 *    The starting length may be non-zero, hence its counting towards
190
 *    the header_byte calculation and its inclusion in sublen calculation.
191
 *    (All those following start out empty, hence the initialization
192
 *    of their lengths to zero.)
193
 */
194
static ssize_t extend_option(fr_dbuff_t *dbuff, fr_dbuff_marker_t *hdr, size_t len)
195
0
{
196
0
  size_t      header_bytes;
197
0
  uint8_t     type = 0, option_len = 0;
198
0
  fr_dbuff_marker_t dst, tmp;
199
200
  /*
201
   *  This can't follow the convention of operating on
202
   *  a chlld dbuff because it must work on and amidst
203
   *  already-written data.
204
   */
205
206
0
  fr_dbuff_marker(&dst, dbuff);
207
0
  fr_dbuff_marker(&tmp, dbuff);
208
209
0
  fr_dbuff_set(&tmp, hdr);
210
211
  /*
212
   *  Read the current header.
213
   */
214
0
  if (fr_dbuff_out(&type, &tmp) < 0 || fr_dbuff_out(&option_len, &tmp) < 0) {
215
0
  error:
216
0
    fr_dbuff_marker_release(&dst);
217
0
    fr_dbuff_marker_release(&tmp);
218
0
    return -1;
219
0
  }
220
221
0
  len += option_len;
222
223
  /*
224
   *  How many bytes we will need to add for all headers.
225
   */
226
0
  header_bytes = (option_len / 255) * 2;
227
228
  /*
229
   *  No room for the new headers and data, we're done.
230
   */
231
0
  if (fr_dbuff_extend_lowat(NULL, dbuff, header_bytes) < header_bytes) goto error;
232
233
  /*
234
   *  Moving the same data repeatedly in a loop is simpler
235
   *  and less error-prone than anything smarter.
236
   */
237
0
  while (true) {
238
0
    uint8_t sublen;
239
240
0
    sublen = (len > 255) ? 255 : len;
241
242
    /*
243
     *  Write the new header, including the (possibly partial) length.
244
     */
245
0
    fr_dbuff_set(&tmp, fr_dbuff_current(hdr));
246
0
    FR_DBUFF_IN_BYTES_RETURN(&tmp, type, sublen);
247
248
    /*
249
     *  The data is already where it's supposed to be, and the length is in the header, and
250
     *  the length is small.  We're done.
251
     */
252
0
    len -= sublen;
253
0
    if (!len) {
254
0
      fr_dbuff_set(dbuff, fr_dbuff_current(hdr) + sublen + 2);
255
0
      len = sublen;
256
0
      break;
257
0
    }
258
259
    /*
260
     *  Take the current header, skip it, and then skip the data we just encoded.  That is the
261
     *  location of the "next" header.
262
     */
263
0
    fr_dbuff_set(&tmp, fr_dbuff_current(hdr) + 2 + 255);
264
0
    fr_dbuff_set(hdr, &tmp);
265
266
    /*
267
     *  The data is currently overlapping with the next header.  We have to move it two bytes forward to
268
     *  make room for the header.
269
     */
270
0
    fr_dbuff_set(&dst, fr_dbuff_current(&tmp) + 2);
271
0
    fr_dbuff_move(&dst, &tmp, len);
272
0
  }
273
274
0
  fr_dbuff_marker_release(&dst);
275
0
  fr_dbuff_marker_release(&tmp);
276
0
  return len;
277
0
}
278
279
#define DHCPV4_OPT_HDR_LEN (2)
280
281
/** Write out an RFC option header and option data
282
 *
283
 * @note May coalesce options with fixed width values
284
 *
285
 * @param[out] dbuff    buffer to write the TLV to.
286
 * @param[in] da_stack    Describing nesting of options.
287
 * @param[in] depth   in the da_stack.
288
 * @param[in,out] cursor  Current attribute we're encoding.
289
 * @param[in] encode_ctx  Containing DHCPv4 dictionary.
290
 * @return
291
 *  - >0 length of data encoded.
292
 *  - 0 if we ran out of space.
293
 *  - < 0 on error.
294
 */
295
static ssize_t encode_rfc(fr_dbuff_t *dbuff,
296
            fr_da_stack_t *da_stack, unsigned int depth,
297
            fr_dcursor_t *cursor, void *encode_ctx)
298
0
{
299
0
  ssize_t     len;
300
0
  fr_dbuff_marker_t hdr;
301
0
  fr_dict_attr_t const  *da = da_stack->da[depth];
302
0
  fr_dbuff_t    work_dbuff = FR_DBUFF(dbuff);
303
304
0
  FR_PROTO_STACK_PRINT(da_stack, depth);
305
306
  /*
307
   *  Write out the option number and length (which, unlike RADIUS,
308
   *  is just the length of the value and hence starts out as zero).
309
   */
310
0
  fr_dbuff_marker(&hdr, &work_dbuff);
311
0
  FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, (uint8_t)da->attr, (uint8_t) 0);
312
313
  /*
314
   *  Write out the option's value
315
   */
316
0
  if (da->flags.array) {
317
0
    len = fr_pair_array_to_network(&work_dbuff, da_stack, depth, cursor, encode_ctx, encode_value);
318
0
    if (len < 0) return -1;
319
320
0
  } else if (da->parent && (da->parent->type != FR_TYPE_VENDOR)) {
321
0
    fr_pair_t *vp;
322
323
0
    do {
324
0
      len = encode_value(&work_dbuff, da_stack, depth, cursor, encode_ctx);
325
0
      if (len < 0) return len; /* @todo return the correct offset, but whatever */
326
327
0
      vp = fr_dcursor_current(cursor);
328
0
    } while (vp && (vp->da == da));
329
330
0
  } else {
331
    /*
332
     *  For VSAs, each vendor value is prefixed by an 8-bit length, so we don't loop over the
333
     *  input pairs.
334
     */
335
0
    len = encode_value(&work_dbuff, da_stack, depth, cursor, encode_ctx);
336
0
    if (len < 0) return len; /* @todo return the correct offset, but whatever */
337
0
  }
338
339
0
  len = fr_dbuff_used(&work_dbuff) - 2;
340
341
0
  if (len <= UINT8_MAX) {
342
0
    fr_dbuff_advance(&hdr, 1);
343
0
    FR_DBUFF_IN_RETURN(&hdr, (uint8_t) len);
344
345
0
  } else if (extend_option(&work_dbuff, &hdr, len) < 0) {
346
0
    return PAIR_ENCODE_FATAL_ERROR;
347
0
  }
348
349
0
  FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), "Done RFC header");
350
351
0
  return fr_dbuff_set(dbuff, &work_dbuff);
352
0
}
353
354
static ssize_t encode_vsio(fr_dbuff_t *dbuff,
355
             fr_da_stack_t *da_stack, unsigned int depth,
356
             fr_dcursor_t *cursor, void *encode_ctx);
357
358
static ssize_t encode_tlv(fr_dbuff_t *dbuff,
359
            fr_da_stack_t *da_stack, unsigned int depth,
360
            fr_dcursor_t *cursor, void *encode_ctx);
361
362
static ssize_t encode_child(fr_dbuff_t *dbuff,
363
          fr_da_stack_t *da_stack, unsigned int depth,
364
          fr_dcursor_t *cursor, void *encode_ctx)
365
0
{
366
0
  ssize_t len;
367
0
  fr_pair_t *vp = fr_dcursor_current(cursor);
368
0
  fr_dcursor_t child_cursor;
369
0
  fr_dbuff_t work_dbuff;
370
371
0
  if (da_stack->da[depth]) {
372
    /*
373
     *  Determine the nested type and call the appropriate encoder
374
     */
375
0
    switch (da_stack->da[depth]->type) {
376
0
    case FR_TYPE_TLV:
377
0
      if (!da_stack->da[depth + 1]) break;
378
379
0
      return encode_tlv(dbuff, da_stack, depth, cursor, encode_ctx);
380
381
0
    case FR_TYPE_VSA:
382
0
      if (!da_stack->da[depth + 1]) break;
383
384
0
      return encode_vsio(dbuff, da_stack, depth, cursor, encode_ctx);
385
386
0
    default:
387
0
      return encode_rfc(dbuff, da_stack, depth, cursor, encode_ctx);
388
0
    }
389
0
  }
390
391
0
  fr_assert(fr_type_is_structural(vp->vp_type));
392
393
0
  fr_pair_dcursor_child_iter_init(&child_cursor, &vp->vp_group, cursor);
394
0
  work_dbuff = FR_DBUFF(dbuff);
395
396
0
  while ((vp = fr_dcursor_current(&child_cursor)) != NULL) {
397
0
    fr_proto_da_stack_build(da_stack, vp->da);
398
399
0
    switch (da_stack->da[depth]->type) {
400
0
    case FR_TYPE_VSA:
401
0
      len = encode_vsio(&work_dbuff, da_stack, depth, &child_cursor, encode_ctx);
402
0
      break;
403
404
0
    case FR_TYPE_TLV:
405
0
      len = encode_tlv(&work_dbuff, da_stack, depth, &child_cursor, encode_ctx);
406
0
      break;
407
408
0
    default:
409
0
      len = encode_rfc(&work_dbuff, da_stack, depth, &child_cursor, encode_ctx);
410
0
      break;
411
0
    }
412
413
0
    if (len <= 0) return len;
414
0
  }
415
416
  /*
417
   *  Skip over the attribute we just encoded.
418
   */
419
0
  vp = fr_dcursor_next(cursor);
420
0
  fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL);
421
422
0
  return fr_dbuff_set(dbuff, &work_dbuff);
423
0
}
424
425
426
427
/** Write out a TLV header (and any sub TLVs or values)
428
 *
429
 * @param[out] dbuff    buffer to write the TLV to.
430
 * @param[in] da_stack    Describing nesting of options.
431
 * @param[in] depth   in the da_stack.
432
 * @param[in,out] cursor  Current attribute we're encoding.
433
 * @param[in] encode_ctx  Containing DHCPv4 dictionary.
434
 * @return
435
 *  - >0 length of data encoded.
436
 *  - 0 if we ran out of space.
437
 *  - < 0 on error.
438
 */
439
static ssize_t encode_tlv(fr_dbuff_t *dbuff,
440
            fr_da_stack_t *da_stack, unsigned int depth,
441
            fr_dcursor_t *cursor, void *encode_ctx)
442
0
{
443
0
  ssize_t     len, option_len;
444
0
  fr_dbuff_t    work_dbuff = FR_DBUFF(dbuff);
445
0
  fr_dbuff_marker_t hdr, dst, tmp;
446
0
  fr_pair_t const   *vp = fr_dcursor_current(cursor);
447
0
  fr_dict_attr_t const  *da = da_stack->da[depth];
448
0
  uint8_t     option_number;
449
450
0
  FR_PROTO_STACK_PRINT(da_stack, depth);
451
452
  /*
453
   *  Where the TLV header starts.
454
   */
455
0
  fr_dbuff_marker(&hdr, &work_dbuff);
456
457
  /*
458
   *  These are set before use; their initial value doesn't matter.
459
   */
460
0
  fr_dbuff_marker(&dst, &work_dbuff);
461
0
  fr_dbuff_marker(&tmp, &work_dbuff);
462
463
  /*
464
   *  Write out the option number and length (which, unlike RADIUS,
465
   *  is just the length of the value and hence starts out as zero).
466
   */
467
0
  option_number = (uint8_t)da->attr;
468
0
  option_len = 0;
469
0
  FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, option_number, option_len);
470
471
  /*
472
   *  Encode any sub TLVs or values
473
   */
474
0
  while (fr_dbuff_extend_lowat(NULL, &work_dbuff, 3) >= 3) {
475
0
    len = encode_child(&work_dbuff, da_stack, depth + 1, cursor, encode_ctx);
476
0
    if (len < 0) return len;
477
0
    if (len == 0) break;   /* Insufficient space */
478
479
    /*
480
     *  If the newly added data fits within the current option, then
481
     *  update the header, and go to the next option.
482
     */
483
0
    if ((option_len + len) <= 255) {
484
0
      option_len += len;
485
486
0
      fr_dbuff_set(&tmp, fr_dbuff_current(&hdr) + 1);
487
0
      FR_DBUFF_IN_BYTES_RETURN(&tmp, (uint8_t) option_len);
488
489
0
    } else if ((len = extend_option(&work_dbuff, &hdr, len)) < 0) {
490
0
      return PAIR_ENCODE_FATAL_ERROR;
491
492
0
    } else {
493
0
      option_len = len;
494
0
    }
495
496
0
    FR_PROTO_STACK_PRINT(da_stack, depth);
497
0
    FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), "TLV header and sub TLVs");
498
499
    /*
500
     *  If nothing updated the attribute, stop
501
     */
502
0
    if (!fr_dcursor_current(cursor) || (vp == fr_dcursor_current(cursor))) break;
503
504
    /*
505
     *  We can encode multiple sub TLVs, if after
506
     *  rebuilding the TLV Stack, the attribute
507
     *  at this depth is the same.
508
     */
509
0
    if ((da != da_stack->da[depth]) || (da_stack->depth < da->depth)) break;
510
0
    vp = fr_dcursor_current(cursor);
511
0
  }
512
513
0
  return fr_dbuff_set(dbuff, &work_dbuff);
514
0
}
515
516
static ssize_t encode_vsio_data(fr_dbuff_t *dbuff,
517
        fr_da_stack_t *da_stack, unsigned int depth,
518
        fr_dcursor_t *cursor, void *encode_ctx)
519
0
{
520
0
  fr_dbuff_t    work_dbuff = FR_DBUFF_MAX(dbuff, 255 - 4 - 1 - 2);
521
0
  fr_dbuff_marker_t hdr;
522
0
  fr_dict_attr_t const  *da;
523
0
  fr_dict_attr_t const  *dv = da_stack->da[depth - 1];
524
0
  ssize_t     len;
525
0
  fr_pair_t   *vp;
526
527
0
  FR_PROTO_STACK_PRINT(da_stack, depth);
528
529
0
  if (dv->type != FR_TYPE_VENDOR) {
530
0
    fr_strerror_printf("%s: Expected type \"vendor\" got \"%s\"", __FUNCTION__,
531
0
           fr_type_to_str(dv->type));
532
0
    return PAIR_ENCODE_FATAL_ERROR;
533
0
  }
534
535
  /*
536
   *  Check if we have enough the enterprise-number,
537
   *  plus the data length, plus at least one option header.
538
   */
539
0
  FR_DBUFF_REMAINING_RETURN(&work_dbuff, sizeof(uint32_t) + 3);
540
541
0
  fr_dbuff_marker(&hdr, &work_dbuff);
542
543
  /*
544
   *  Copy in the 32bit PEN (Private Enterprise Number)
545
   *
546
   *  And leave room for data-len1
547
   */
548
0
  FR_DBUFF_IN_RETURN(&work_dbuff, dv->attr);
549
0
  FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, (uint8_t) 0x00);
550
551
  /*
552
   *  https://tools.ietf.org/html/rfc3925#section-4
553
   *
554
   *                         1 1 1 1 1 1
555
   *     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
556
   *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
557
   *    |  option-code  |  option-len   |
558
   *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
559
   *    |      enterprise-number1       |
560
   *    |                               |
561
   *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
562
   *    |   data-len1   |               |
563
   *    +-+-+-+-+-+-+-+-+ option-data1  |
564
   *    /                               /
565
   *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
566
   */
567
0
  da = da_stack->da[depth];
568
569
  /*
570
   *  RFC 3925 Section 4 says:
571
   *
572
   *  Multiple instances of this option may be present and MUST be concatenated in accordance with
573
   *  RFC 3396.
574
   *
575
   *  @todo - we don't currently allow encoding more data as per extend_option() or encode_tlv().
576
   *  We probably want to do that.  We probably also want to update the decoder so that it
577
   *  concatenates options before decoding, too.
578
   */
579
0
  while (true) {
580
0
    len = encode_child(&work_dbuff, da_stack, depth, cursor, encode_ctx);
581
0
    if (len == 0) break; /* insufficient space */
582
0
    if (len < 0) return len;
583
584
0
    vp = fr_dcursor_current(cursor);
585
0
    if (!vp) break;
586
587
    /*
588
     *  Encode all attributes which match this vendor.
589
     */
590
0
    if (vp->da->parent != da->parent) break;
591
0
  }
592
593
  /*
594
   *  Write out "data-len1" for this vendor
595
   */
596
0
  fr_dbuff_advance(&hdr, 4);
597
0
  FR_DBUFF_IN_RETURN(&hdr, (uint8_t)(fr_dbuff_used(&work_dbuff) - 4 - 1));
598
599
0
#ifndef NDEBUG
600
0
  FR_PROTO_HEX_DUMP(dbuff->p, fr_dbuff_used(&work_dbuff), "Done VSIO Data");
601
0
#endif
602
603
0
  return fr_dbuff_set(dbuff, &work_dbuff);
604
0
}
605
606
static ssize_t encode_vsio(fr_dbuff_t *dbuff,
607
             fr_da_stack_t *da_stack, unsigned int depth,
608
             fr_dcursor_t *cursor, void *encode_ctx)
609
0
{
610
0
  fr_dict_attr_t const  *da = da_stack->da[depth];
611
0
  fr_pair_t   *vp;
612
0
  fr_dcursor_t    vendor_cursor;
613
0
  fr_dbuff_t    work_dbuff;
614
0
  fr_dbuff_marker_t hdr;
615
616
0
  FR_PROTO_STACK_PRINT(da_stack, depth);
617
618
  /*
619
   *  DA should be a VSA type with the value of OPTION_VENDOR_OPTS.
620
   */
621
0
  if (da->type != FR_TYPE_VSA) {
622
0
    fr_strerror_printf("%s: Expected type \"vsa\" got \"%s\"", __FUNCTION__,
623
0
           fr_type_to_str(da->type));
624
0
    return PAIR_ENCODE_FATAL_ERROR;
625
0
  }
626
627
0
  work_dbuff = FR_DBUFF(dbuff);
628
0
  fr_dbuff_marker(&hdr, &work_dbuff);
629
630
  /*
631
   *  Copy in the option code
632
   *  And leave room for data-len1
633
   */
634
0
  FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, (uint8_t) da->attr, 0x00);
635
636
  /*
637
   *  We are at the VSA.  The next entry in the stack is the vendor.  The entry after that is the vendor data.
638
   */
639
0
  if (da_stack->da[depth + 1]) {
640
0
    ssize_t len;
641
0
    fr_dcursor_t vsa_cursor;
642
643
0
    if (da_stack->da[depth + 2]) {
644
0
      len = encode_vsio_data(&work_dbuff, da_stack, depth + 2, cursor, encode_ctx);
645
0
      if (len <= 0) return len;
646
0
      goto done;
647
0
    }
648
649
0
    vp = fr_dcursor_current(cursor);
650
0
    fr_assert(vp->vp_type == FR_TYPE_VENDOR);
651
652
    /*
653
     *  Copied from below.
654
     */
655
0
    fr_pair_dcursor_init(&vsa_cursor, &vp->vp_group);
656
0
    work_dbuff = FR_DBUFF(dbuff);
657
658
0
    while ((vp = fr_dcursor_current(&vsa_cursor)) != NULL) {
659
0
      fr_proto_da_stack_build(da_stack, vp->da);
660
0
      len = encode_vsio_data(&work_dbuff, da_stack, depth + 2, &vsa_cursor, encode_ctx);
661
0
      if (len <= 0) return len;
662
0
    }
663
0
    goto done;
664
0
  }
665
666
0
  vp = fr_dcursor_current(cursor);
667
0
  fr_assert(vp->da == da);
668
669
0
  fr_pair_dcursor_init(&vendor_cursor, &vp->vp_group);
670
671
  /*
672
   *  Loop over all vendors, and inside of that, loop over all VSA attributes.
673
   */
674
0
  while ((vp = fr_dcursor_current(&vendor_cursor)) != NULL) {
675
0
    ssize_t len;
676
0
    fr_dcursor_t vsa_cursor;
677
678
0
    if (vp->vp_type != FR_TYPE_VENDOR) {
679
0
      (void) fr_dcursor_next(&vendor_cursor);
680
0
      continue;
681
0
    }
682
683
0
    fr_pair_dcursor_init(&vsa_cursor, &vp->vp_group);
684
685
0
    while ((vp = fr_dcursor_current(&vsa_cursor)) != NULL) {
686
      /*
687
       *  RFC 3925 Section 4 says:
688
       *
689
       *  "An Enterprise Number SHOULD only occur once
690
       *  among all instances of this option.  Behavior
691
       *  is undefined if an Enterprise Number occurs
692
       *  multiple times."
693
       *
694
       *  The function encode_vsio_data() builds
695
       *  one header, and then loops over all
696
       *  children of the vsa_cursor.
697
       */
698
0
      fr_proto_da_stack_build(da_stack, vp->da);
699
0
      len = encode_vsio_data(&work_dbuff, da_stack, depth + 2, &vsa_cursor, encode_ctx);
700
0
      if (len < 0) return len;
701
702
0
      if (len == 0) (void) fr_dcursor_next(&vsa_cursor);
703
0
    }
704
705
0
    (void) fr_dcursor_next(&vendor_cursor);
706
0
  }
707
708
  /*
709
   *  Write out length for whole option
710
   */
711
0
done:
712
0
  fr_dbuff_advance(&hdr, 1);
713
0
  FR_DBUFF_IN_RETURN(&hdr, (uint8_t)(fr_dbuff_used(&work_dbuff) - DHCPV4_OPT_HDR_LEN));
714
715
  /*
716
   *  Skip over the attribute we just encoded.
717
   */
718
0
  vp = fr_dcursor_next(cursor);
719
0
  fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL);
720
721
0
  return fr_dbuff_set(dbuff, &work_dbuff);
722
0
}
723
724
/** Encode a DHCP option and any sub-options.
725
 *
726
 * @param[out] dbuff    Where to write encoded DHCP attributes.
727
 * @param[in] cursor    with current VP set to the option to be encoded.
728
 *        Will be advanced to the next option to encode.
729
 * @param[in] encode_ctx  Containing DHCPv4 dictionary.
730
 * @return
731
 *  - > 0 length of data written.
732
 *  - < 0 error.
733
 *  - 0 not valid option for DHCP (skipping).
734
 */
735
ssize_t fr_dhcpv4_encode_option(fr_dbuff_t *dbuff, fr_dcursor_t *cursor, void *encode_ctx)
736
0
{
737
0
  fr_pair_t   *vp;
738
0
  fr_dhcpv4_ctx_t   *enc_ctx = encode_ctx;
739
0
  unsigned int    depth = enc_ctx->root->depth;
740
0
  fr_da_stack_t   da_stack;
741
0
  ssize_t     len;
742
0
  fr_dbuff_t    work_dbuff = FR_DBUFF(dbuff);
743
744
0
  vp = fr_dcursor_current(cursor);
745
0
  if (!vp) return -1;
746
747
0
  fr_proto_da_stack_build(&da_stack, vp->da);
748
749
0
  FR_PROTO_STACK_PRINT(&da_stack, depth);
750
751
  /*
752
   *  We only have two types of options in DHCPv4
753
   */
754
0
  switch (da_stack.da[depth]->type) {
755
0
  case FR_TYPE_VSA:
756
0
    len = encode_vsio(&work_dbuff, &da_stack, depth, cursor, encode_ctx);
757
0
    break;
758
759
0
  case FR_TYPE_TLV:
760
0
    len = encode_tlv(&work_dbuff, &da_stack, depth, cursor, encode_ctx);
761
0
    break;
762
763
0
  case FR_TYPE_GROUP:
764
0
  case FR_TYPE_STRUCT:
765
0
  case FR_TYPE_LEAF:
766
0
    len = encode_rfc(&work_dbuff, &da_stack, depth, cursor, encode_ctx);
767
0
    break;
768
769
0
  default:
770
0
    fr_strerror_printf("DHCP option %s has unsupported data type '%s'",
771
0
           da_stack.da[depth]->name, fr_type_to_str(da_stack.da[depth]->type));
772
773
0
    return -1;
774
0
  }
775
776
0
  if (len <= 0) return len;
777
778
0
  FR_PROTO_TRACE("Complete option is %zu byte(s)", fr_dbuff_used(&work_dbuff));
779
0
  FR_PROTO_HEX_DUMP(dbuff->p, fr_dbuff_used(&work_dbuff), NULL);
780
781
0
  return fr_dbuff_set(dbuff, &work_dbuff);
782
0
}
783
784
ssize_t fr_dhcpv4_encode_foreign(fr_dbuff_t *dbuff, fr_pair_list_t const *list)
785
0
{
786
0
  ssize_t   slen;
787
0
  fr_dcursor_t  cursor;
788
0
  fr_dbuff_t  work_dbuff = FR_DBUFF(dbuff);
789
790
0
  fr_assert(dict_dhcpv4 != NULL);
791
792
0
  fr_pair_dcursor_iter_init(&cursor, list, fr_dhcpv4_next_encodable, dict_dhcpv4);
793
794
  /*
795
   *  Loop over all DHCPv4 options.
796
   *
797
   *  Unlike fr_dhcpv4_encode_dbuff(), we don't sort the options.  If that causes problems, we will
798
   *  deal with it later.
799
   */
800
0
  while (fr_dcursor_current(&cursor) != NULL) {
801
0
    slen = fr_dhcpv4_encode_option(&work_dbuff, &cursor, &(fr_dhcpv4_ctx_t){ .root = fr_dict_root(dict_dhcpv4) });
802
0
    if (slen < 0) return slen;
803
0
  }
804
805
0
  FR_PROTO_TRACE("Foreign option is %zu byte(s)", fr_dbuff_used(&work_dbuff));
806
0
  FR_PROTO_HEX_DUMP(dbuff->p, fr_dbuff_used(&work_dbuff), NULL);
807
808
0
  return fr_dbuff_set(dbuff, &work_dbuff);
809
0
}
810
811
static ssize_t fr_dhcpv4_encode_proto(UNUSED TALLOC_CTX *ctx, fr_pair_list_t *vps, uint8_t *data, size_t data_len, UNUSED void *proto_ctx)
812
0
{
813
0
  return fr_dhcpv4_encode_dbuff(&FR_DBUFF_TMP(data, data_len), NULL, 0, 0, vps);
814
0
}
815
816
static int encode_test_ctx(void **out, TALLOC_CTX *ctx, UNUSED fr_dict_t const *dict,
817
         fr_dict_attr_t const *root_da)
818
1.25k
{
819
1.25k
  fr_dhcpv4_ctx_t *test_ctx;
820
821
1.25k
  test_ctx = talloc_zero(ctx, fr_dhcpv4_ctx_t);
822
1.25k
  if (!test_ctx) return -1;
823
1.25k
  test_ctx->root = root_da ? root_da : fr_dict_root(dict_dhcpv4);
824
825
1.25k
  *out = test_ctx;
826
827
1.25k
  return 0;
828
1.25k
}
829
830
/*
831
 *  Test points
832
 */
833
extern fr_test_point_pair_encode_t dhcpv4_tp_encode_pair;
834
fr_test_point_pair_encode_t dhcpv4_tp_encode_pair = {
835
  .test_ctx = encode_test_ctx,
836
  .func   = fr_dhcpv4_encode_option,
837
  .next_encodable = fr_dhcpv4_next_encodable,
838
};
839
840
841
842
extern fr_test_point_proto_encode_t dhcpv4_tp_encode_proto;
843
fr_test_point_proto_encode_t dhcpv4_tp_encode_proto = {
844
  .test_ctx = encode_test_ctx,
845
  .func   = fr_dhcpv4_encode_proto
846
};