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