/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 | | }; |