/src/freeradius-server/src/protocols/dhcpv6/base.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: 6b104bedba0e57849d9e5f3d32cc128d9e2ca3ad $ |
19 | | * |
20 | | * @file protocols/dhcpv6/base.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 <freeradius-devel/io/pair.h> |
29 | | #include <freeradius-devel/protocol/dhcpv6/freeradius.internal.h> |
30 | | #include <freeradius-devel/protocol/dhcpv6/rfc3315.h> |
31 | | #include <freeradius-devel/protocol/dhcpv6/rfc5007.h> |
32 | | #include <freeradius-devel/util/proto.h> |
33 | | #include <freeradius-devel/util/rand.h> |
34 | | |
35 | | #include "dhcpv6.h" |
36 | | #include "attrs.h" |
37 | | |
38 | | static uint32_t instance_count = 0; |
39 | | static bool instantiated = false; |
40 | | |
41 | | fr_dict_t const *dict_dhcpv6; |
42 | | |
43 | | extern fr_dict_autoload_t libfreeradius_dhcpv6_dict[]; |
44 | | fr_dict_autoload_t libfreeradius_dhcpv6_dict[] = { |
45 | | { .out = &dict_dhcpv6, .proto = "dhcpv6" }, |
46 | | DICT_AUTOLOAD_TERMINATOR |
47 | | }; |
48 | | |
49 | | fr_dict_attr_t const *attr_packet_type; |
50 | | fr_dict_attr_t const *attr_transaction_id; |
51 | | fr_dict_attr_t const *attr_hop_count; |
52 | | fr_dict_attr_t const *attr_relay_link_address; |
53 | | fr_dict_attr_t const *attr_relay_peer_address; |
54 | | fr_dict_attr_t const *attr_relay_message; |
55 | | fr_dict_attr_t const *attr_option_request; |
56 | | |
57 | | extern fr_dict_attr_autoload_t libfreeradius_dhcpv6_dict_attr[]; |
58 | | fr_dict_attr_autoload_t libfreeradius_dhcpv6_dict_attr[] = { |
59 | | { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_dhcpv6 }, |
60 | | { .out = &attr_transaction_id, .name = "Transaction-Id", .type = FR_TYPE_OCTETS, .dict = &dict_dhcpv6 }, |
61 | | { .out = &attr_hop_count, .name = "Hop-Count", .type = FR_TYPE_UINT8, .dict = &dict_dhcpv6 }, |
62 | | { .out = &attr_relay_link_address, .name = "Relay-Link-Address", .type = FR_TYPE_IPV6_ADDR, .dict = &dict_dhcpv6 }, |
63 | | { .out = &attr_relay_peer_address, .name = "Relay-Peer-Address", .type = FR_TYPE_IPV6_ADDR, .dict = &dict_dhcpv6 }, |
64 | | { .out = &attr_relay_message, .name = "Relay-Message", .type = FR_TYPE_GROUP, .dict = &dict_dhcpv6 }, |
65 | | { .out = &attr_option_request, .name = "Option-Request", .type = FR_TYPE_ATTR, .dict = &dict_dhcpv6 }, |
66 | | DICT_AUTOLOAD_TERMINATOR |
67 | | }; |
68 | | |
69 | | /* |
70 | | * grep VALUE share/dictionary/dhcpv6/dictionary.freeradius.internal | awk '{print "[" $4 "] = \"" $3 "\"," }' |
71 | | */ |
72 | | char const *fr_dhcpv6_packet_names[FR_DHCPV6_CODE_MAX] = { |
73 | | [0] = "invalid", |
74 | | [FR_PACKET_TYPE_VALUE_SOLICIT] = "Solicit", |
75 | | [FR_PACKET_TYPE_VALUE_ADVERTISE] = "Advertise", |
76 | | [FR_PACKET_TYPE_VALUE_REQUEST] = "Request", |
77 | | [FR_PACKET_TYPE_VALUE_CONFIRM] = "Confirm", |
78 | | [FR_PACKET_TYPE_VALUE_RENEW] = "Renew", |
79 | | [FR_PACKET_TYPE_VALUE_REBIND] = "Rebind", |
80 | | [FR_PACKET_TYPE_VALUE_REPLY] = "Reply", |
81 | | [FR_PACKET_TYPE_VALUE_RELEASE] = "Release", |
82 | | [FR_PACKET_TYPE_VALUE_DECLINE] = "Decline", |
83 | | [FR_PACKET_TYPE_VALUE_RECONFIGURE] = "Reconfigure", |
84 | | [FR_PACKET_TYPE_VALUE_INFORMATION_REQUEST] = "Information-Request", |
85 | | [FR_PACKET_TYPE_VALUE_RELAY_FORWARD] = "Relay-Forward", |
86 | | [FR_PACKET_TYPE_VALUE_RELAY_REPLY] = "Relay-Reply", |
87 | | [FR_PACKET_TYPE_VALUE_LEASE_QUERY] = "Lease-Query", |
88 | | [FR_PACKET_TYPE_VALUE_LEASE_QUERY_REPLY] = "Lease-Query-Reply", |
89 | | [FR_PACKET_TYPE_VALUE_LEASE_QUERY_DONE] = "Lease-Query-Done", |
90 | | [FR_PACKET_TYPE_VALUE_LEASE_QUERY_DATA] = "Lease-Query-Data", |
91 | | [FR_PACKET_TYPE_VALUE_RECONFIGURE_REQUEST] = "Reconfigure-Request", |
92 | | [FR_PACKET_TYPE_VALUE_RECONFIGURE_REPLY] = "Reconfigure-Reply", |
93 | | [FR_PACKET_TYPE_VALUE_DHCPV4_QUERY] = "DHCPv4-Query", |
94 | | [FR_PACKET_TYPE_VALUE_DHCPV4_RESPONSE] = "DHCPv4-Response", |
95 | | [FR_PACKET_TYPE_VALUE_ACTIVE_LEASE_QUERY] = "Active-Lease-Query", |
96 | | [FR_PACKET_TYPE_VALUE_START_TLS] = "Start-TLS", |
97 | | [FR_PACKET_TYPE_VALUE_BIND_UPDATE] = "Bind-Update", |
98 | | [FR_PACKET_TYPE_VALUE_BIND_REPLY] = "Bind-Reply", |
99 | | [FR_PACKET_TYPE_VALUE_POOL_REQUEST] = "Pool-Request", |
100 | | [FR_PACKET_TYPE_VALUE_POOL_RESPONSE] = "Pool-Response", |
101 | | [FR_PACKET_TYPE_VALUE_UPDATE_REQUEST] = "Update-Request", |
102 | | [FR_PACKET_TYPE_VALUE_UPDATE_REQUEST_ALL] = "Update-Request-All", |
103 | | [FR_PACKET_TYPE_VALUE_UPDATE_DONE] = "Update-Done", |
104 | | [FR_PACKET_TYPE_VALUE_CONNECT] = "Connect", |
105 | | [FR_PACKET_TYPE_VALUE_CONNECT_REPLY] = "Connect-Reply", |
106 | | [FR_PACKET_TYPE_VALUE_DISCONNECT] = "Disconnect", |
107 | | [FR_PACKET_TYPE_VALUE_STATE] = "State", |
108 | | [FR_PACKET_TYPE_VALUE_CONTACT] = "Contact" |
109 | | }; |
110 | | |
111 | | FR_DICT_ATTR_FLAG_FUNC(fr_dhcpv6_attr_flags_t, dns_label) |
112 | | FR_DICT_ATTR_FLAG_FUNC(fr_dhcpv6_attr_flags_t, partial_dns_label) |
113 | | |
114 | | static fr_dict_flag_parser_t const dhcpv6_flags[] = { |
115 | | { L("dns_label"), { .func = dict_flag_dns_label } }, |
116 | | { L("partial_dns_label"), { .func = dict_flag_partial_dns_label } } |
117 | | }; |
118 | | |
119 | | static ssize_t fr_dhcpv6_ok_internal(uint8_t const *packet, uint8_t const *end, size_t max_attributes, int depth); |
120 | | |
121 | | static ssize_t fr_dhcpv6_options_ok(uint8_t const *packet, uint8_t const *end, size_t max_attributes, |
122 | | bool allow_relay, int depth) |
123 | 0 | { |
124 | 0 | size_t attributes; |
125 | 0 | uint8_t const *p; |
126 | |
|
127 | 0 | attributes = 0; |
128 | 0 | p = packet; |
129 | |
|
130 | 0 | while (p < end) { |
131 | 0 | uint16_t len; |
132 | |
|
133 | 0 | if ((size_t)(end - p) < DHCPV6_OPT_HDR_LEN) { |
134 | 0 | fr_strerror_const("Not enough room for option header"); |
135 | 0 | return -(p - packet); |
136 | 0 | } |
137 | | |
138 | 0 | len = DHCPV6_GET_OPTION_LEN(p); |
139 | 0 | if ((size_t)(end - p) < (DHCPV6_OPT_HDR_LEN + len)) { |
140 | 0 | fr_strerror_const("Option length overflows the packet"); |
141 | 0 | return -(p - packet); |
142 | 0 | } |
143 | | |
144 | 0 | attributes++; |
145 | 0 | if (attributes > (size_t) max_attributes) { |
146 | 0 | fr_strerror_const("Too many attributes"); |
147 | 0 | return -(p - packet); |
148 | 0 | } |
149 | | |
150 | | /* |
151 | | * Recurse into the Relay-Message attribute, but |
152 | | * only if the outer packet was a relayed message. |
153 | | */ |
154 | 0 | if (allow_relay && (p[0] == 0) && (p[1] == attr_relay_message->attr)) { |
155 | 0 | ssize_t child; |
156 | | |
157 | | /* |
158 | | * Recurse to check the encapsulated packet. |
159 | | */ |
160 | 0 | child = fr_dhcpv6_ok_internal(p + 4, p + 4 + len, max_attributes - attributes, depth + 1); |
161 | 0 | if (child <= 0) return -((p + 4) - packet) + child; |
162 | | |
163 | 0 | attributes += child; |
164 | 0 | } |
165 | | |
166 | 0 | p += DHCPV6_OPT_HDR_LEN + len; |
167 | 0 | } |
168 | | |
169 | 0 | return attributes; |
170 | 0 | } |
171 | | |
172 | | static ssize_t fr_dhcpv6_ok_internal(uint8_t const *packet, uint8_t const *end, size_t max_attributes, int depth) |
173 | 0 | { |
174 | 0 | uint8_t const *p; |
175 | 0 | ssize_t attributes; |
176 | 0 | bool allow_relay; |
177 | 0 | size_t packet_len = end - packet; |
178 | |
|
179 | 0 | if (end == packet) { |
180 | 0 | fr_strerror_const("Packet is empty"); |
181 | 0 | return 0; |
182 | 0 | } |
183 | | |
184 | 0 | if (depth > DHCPV6_MAX_RELAY_NESTING) { |
185 | 0 | fr_strerror_const("Too many layers forwarded packets"); |
186 | 0 | return 0; |
187 | 0 | } |
188 | | |
189 | 0 | switch (packet[0]) { |
190 | 0 | case FR_DHCPV6_RELAY_FORWARD: |
191 | 0 | case FR_DHCPV6_RELAY_REPLY: |
192 | 0 | if (packet_len < DHCPV6_RELAY_HDR_LEN) { |
193 | 0 | fr_strerror_const("Packet is too small for relay header"); |
194 | 0 | return 0; |
195 | 0 | } |
196 | | |
197 | 0 | p = packet + DHCPV6_RELAY_HDR_LEN; |
198 | 0 | allow_relay = true; |
199 | 0 | break; |
200 | | |
201 | 0 | default: |
202 | | /* |
203 | | * 8 bit code + 24 bits of transaction ID |
204 | | */ |
205 | 0 | if (packet_len < DHCPV6_HDR_LEN) { |
206 | 0 | fr_strerror_const("Packet is too small for DHCPv6 header"); |
207 | 0 | return 0; |
208 | 0 | } |
209 | | |
210 | 0 | p = packet + DHCPV6_HDR_LEN; |
211 | 0 | allow_relay = false; |
212 | 0 | break; |
213 | 0 | } |
214 | | |
215 | 0 | attributes = fr_dhcpv6_options_ok(p, end, max_attributes, allow_relay, depth); |
216 | 0 | if (attributes < 0) return -(p - packet) + attributes; |
217 | | |
218 | 0 | return attributes; |
219 | 0 | } |
220 | | |
221 | | |
222 | | /** See if the data pointed to by PTR is a valid DHCPv6 packet. |
223 | | * |
224 | | * @param[in] packet to check. |
225 | | * @param[in] packet_len The size of the packet data. |
226 | | * @param[in] max_attributes to allow in the packet. |
227 | | * @return |
228 | | * - True on success. |
229 | | * - False on failure. |
230 | | */ |
231 | | bool fr_dhcpv6_ok(uint8_t const *packet, size_t packet_len, uint32_t max_attributes) |
232 | 0 | { |
233 | 0 | ssize_t slen; |
234 | |
|
235 | 0 | slen = fr_dhcpv6_ok_internal(packet, packet + packet_len, max_attributes, 0); |
236 | 0 | if (slen <= 0) { |
237 | 0 | fr_strerror_printf_push("Invalid DHCPv6 packet starting at offset %zd", -slen); |
238 | 0 | return false; |
239 | 0 | } |
240 | | |
241 | 0 | return true; |
242 | 0 | } |
243 | | |
244 | | /* |
245 | | * Return pointer to a particular option. |
246 | | */ |
247 | | uint8_t const *fr_dhcpv6_option_find(uint8_t const *start, uint8_t const *end, unsigned int option) |
248 | 0 | { |
249 | 0 | uint8_t const *p = start; |
250 | |
|
251 | 0 | while (p < end) { |
252 | 0 | uint16_t found; |
253 | 0 | uint16_t len; |
254 | |
|
255 | 0 | if ((size_t)(end - p) < DHCPV6_OPT_HDR_LEN) return NULL; |
256 | | |
257 | 0 | found = DHCPV6_GET_OPTION_NUM(p); |
258 | 0 | len = DHCPV6_GET_OPTION_LEN(p); |
259 | |
|
260 | 0 | if ((p + DHCPV6_OPT_HDR_LEN + len) > end) return NULL; |
261 | | |
262 | 0 | if (found == option) return p; |
263 | | |
264 | 0 | p += DHCPV6_OPT_HDR_LEN + len; |
265 | 0 | } |
266 | | |
267 | 0 | return NULL; |
268 | 0 | } |
269 | | |
270 | | static bool duid_match(uint8_t const *option, fr_dhcpv6_decode_ctx_t const *packet_ctx) |
271 | 0 | { |
272 | 0 | uint16_t len; |
273 | |
|
274 | 0 | len = DHCPV6_GET_OPTION_LEN(option); |
275 | 0 | if (len != packet_ctx->duid_len) return false; |
276 | 0 | if (memcmp(option + 4, packet_ctx->duid, packet_ctx->duid_len) != 0) return false; |
277 | | |
278 | 0 | return true; |
279 | 0 | } |
280 | | |
281 | | /** Verify a reply packet from a server to a client |
282 | | * |
283 | | */ |
284 | | static bool verify_to_client(uint8_t const *packet, size_t packet_len, fr_dhcpv6_decode_ctx_t const *packet_ctx) |
285 | 0 | { |
286 | 0 | uint32_t transaction_id; |
287 | 0 | uint8_t const *option; |
288 | 0 | uint8_t const *options = packet + 4; |
289 | 0 | uint8_t const *end = packet + packet_len; |
290 | |
|
291 | 0 | switch (packet[0]) { |
292 | 0 | case FR_PACKET_TYPE_VALUE_ADVERTISE: |
293 | 0 | transaction_id = fr_nbo_to_uint24(&packet[1]); |
294 | 0 | if (transaction_id != packet_ctx->transaction_id) { |
295 | 0 | fail_tid: |
296 | 0 | fr_strerror_const("Transaction ID does not match"); |
297 | 0 | return false; |
298 | 0 | } |
299 | | |
300 | 0 | if (!fr_dhcpv6_option_find(options, end, FR_SERVER_ID)) { |
301 | 0 | fail_sid: |
302 | 0 | fr_strerror_const("Packet does not contain a Server-Id option"); |
303 | 0 | return false; |
304 | 0 | } |
305 | | |
306 | 0 | option = fr_dhcpv6_option_find(options, end, FR_CLIENT_ID); |
307 | 0 | if (!option) { |
308 | 0 | fail_cid: |
309 | 0 | fr_strerror_const("Packet does not contain a Client-Id option"); |
310 | 0 | return false; |
311 | 0 | } |
312 | | |
313 | | /* |
314 | | * The DUID MUST exist. |
315 | | */ |
316 | 0 | if (!packet_ctx->duid) { |
317 | 0 | fail_duid: |
318 | 0 | fr_strerror_const("Packet context does not contain a DUID"); |
319 | 0 | return false; |
320 | 0 | } |
321 | | |
322 | 0 | check_duid: |
323 | 0 | if (!duid_match(option, packet_ctx)) { |
324 | 0 | fail_match: |
325 | 0 | fr_strerror_const("DUID in packet does not match our DUID"); |
326 | 0 | return false; |
327 | 0 | } |
328 | 0 | return true; |
329 | | |
330 | 0 | case FR_PACKET_TYPE_VALUE_REPLY: |
331 | 0 | transaction_id = fr_nbo_to_uint24(&packet[1]); |
332 | 0 | if (transaction_id != packet_ctx->transaction_id) goto fail_tid; |
333 | | |
334 | 0 | if (!fr_dhcpv6_option_find(options, end, FR_SERVER_ID)) goto fail_sid; |
335 | | |
336 | | /* |
337 | | * It's OK to not have a client ID in the reply if we didn't send one. |
338 | | */ |
339 | 0 | option = fr_dhcpv6_option_find(options, end, FR_CLIENT_ID); |
340 | 0 | if (!option) { |
341 | 0 | if (!packet_ctx->duid) return true; |
342 | 0 | goto fail_cid; |
343 | 0 | } |
344 | 0 | goto check_duid; |
345 | | |
346 | 0 | case FR_PACKET_TYPE_VALUE_RECONFIGURE: |
347 | 0 | if (!fr_dhcpv6_option_find(options, end, FR_SERVER_ID)) goto fail_sid; |
348 | | |
349 | 0 | option = fr_dhcpv6_option_find(options, end, FR_CLIENT_ID); |
350 | 0 | if (!option) goto fail_cid; |
351 | | |
352 | | /* |
353 | | * The DUID MUST exist. |
354 | | */ |
355 | 0 | if (!packet_ctx->duid) goto fail_duid; |
356 | 0 | if (!duid_match(option, packet_ctx)) goto fail_match; |
357 | | |
358 | 0 | option = fr_dhcpv6_option_find(options, end, FR_RECONF_MSG); |
359 | 0 | if (!option) { |
360 | 0 | fr_strerror_const("Packet does not contain a Reconf-Msg option"); |
361 | 0 | return false; |
362 | 0 | } |
363 | | |
364 | | /* |
365 | | * @todo - check reconfigure message type, and |
366 | | * reject if it doesn't match. |
367 | | */ |
368 | | |
369 | | /* |
370 | | * @todo - check for authentication option and |
371 | | * verify it. |
372 | | */ |
373 | 0 | break; |
374 | | |
375 | 0 | case FR_DHCPV6_RELAY_REPLY: |
376 | 0 | if (packet_len < DHCPV6_RELAY_HDR_LEN) { |
377 | 0 | fr_strerror_const("Relay-Reply message is too small"); |
378 | 0 | return false; |
379 | 0 | } |
380 | | |
381 | 0 | options += (DHCPV6_RELAY_HDR_LEN - 4); /* we assumed it was a normal packet above */ |
382 | 0 | option = fr_dhcpv6_option_find(options, end, FR_RELAY_MESSAGE); |
383 | 0 | if (!option) { |
384 | 0 | fr_strerror_const("Packet does not contain a Relay-Message option"); |
385 | 0 | return false; |
386 | 0 | } |
387 | 0 | return verify_to_client(option + 4, DHCPV6_GET_OPTION_LEN(option), packet_ctx); |
388 | | |
389 | 0 | case FR_DHCPV6_LEASE_QUERY_REPLY: |
390 | 0 | transaction_id = fr_nbo_to_uint24(&packet[1]); |
391 | 0 | if (transaction_id != packet_ctx->transaction_id) goto fail_tid; |
392 | | |
393 | 0 | if (!fr_dhcpv6_option_find(options, end, FR_SERVER_ID)) goto fail_sid; |
394 | | |
395 | 0 | option = fr_dhcpv6_option_find(options, end, FR_CLIENT_ID); |
396 | 0 | if (!option) goto fail_cid; |
397 | | |
398 | | /* |
399 | | * The DUID MUST exist. |
400 | | */ |
401 | 0 | if (!packet_ctx->duid) goto fail_duid; |
402 | 0 | if (!duid_match(option, packet_ctx)) goto fail_match; |
403 | 0 | break; |
404 | | |
405 | 0 | case FR_PACKET_TYPE_VALUE_REQUEST: |
406 | 0 | case FR_PACKET_TYPE_VALUE_CONFIRM: |
407 | 0 | case FR_PACKET_TYPE_VALUE_RENEW: |
408 | 0 | case FR_PACKET_TYPE_VALUE_REBIND: |
409 | 0 | case FR_PACKET_TYPE_VALUE_RELEASE: |
410 | 0 | case FR_PACKET_TYPE_VALUE_DECLINE: |
411 | 0 | case FR_PACKET_TYPE_VALUE_INFORMATION_REQUEST: |
412 | 0 | default: |
413 | 0 | fr_strerror_const("Invalid message type sent to client"); |
414 | 0 | return false; |
415 | 0 | } |
416 | | |
417 | 0 | return true; |
418 | 0 | } |
419 | | |
420 | | |
421 | | /** Verify a packet from a client to a server |
422 | | * |
423 | | */ |
424 | | static bool verify_from_client(uint8_t const *packet, size_t packet_len, fr_dhcpv6_decode_ctx_t const *packet_ctx) |
425 | 0 | { |
426 | 0 | uint8_t const *option; |
427 | 0 | uint8_t const *options = packet + 4; |
428 | 0 | uint8_t const *end = packet + packet_len; |
429 | | |
430 | | /* |
431 | | * Servers MUST have a DUID |
432 | | */ |
433 | 0 | if (!packet_ctx->duid) { |
434 | 0 | fr_strerror_const("Packet context does not contain a DUID"); |
435 | 0 | return false; |
436 | 0 | } |
437 | | |
438 | 0 | switch (packet[0]) { |
439 | 0 | case FR_PACKET_TYPE_VALUE_SOLICIT: |
440 | 0 | case FR_PACKET_TYPE_VALUE_CONFIRM: |
441 | 0 | case FR_PACKET_TYPE_VALUE_REBIND: |
442 | 0 | if (!fr_dhcpv6_option_find(options, end, FR_CLIENT_ID)) { |
443 | 0 | fail_cid: |
444 | 0 | fr_strerror_const("Packet does not contain a Client-Id option"); |
445 | 0 | return false; |
446 | 0 | } |
447 | | |
448 | 0 | if (!fr_dhcpv6_option_find(options, end, FR_SERVER_ID)) { |
449 | 0 | fail_sid: |
450 | 0 | fr_strerror_const("Packet does not contain a Server-Id option"); |
451 | 0 | return false; |
452 | 0 | } |
453 | 0 | break; |
454 | | |
455 | 0 | case FR_PACKET_TYPE_VALUE_REQUEST: |
456 | 0 | case FR_PACKET_TYPE_VALUE_RENEW: |
457 | 0 | case FR_PACKET_TYPE_VALUE_DECLINE: |
458 | 0 | case FR_PACKET_TYPE_VALUE_RELEASE: |
459 | 0 | if (!fr_dhcpv6_option_find(options, end, FR_CLIENT_ID)) goto fail_cid; |
460 | | |
461 | 0 | option = fr_dhcpv6_option_find(options, end, FR_SERVER_ID); |
462 | 0 | if (!option) goto fail_sid; |
463 | | |
464 | 0 | if (!duid_match(option, packet_ctx)) { |
465 | 0 | fail_match: |
466 | 0 | fr_strerror_const("DUID in packet does not match our DUID"); |
467 | 0 | return false; |
468 | 0 | } |
469 | 0 | break; |
470 | | |
471 | 0 | case FR_PACKET_TYPE_VALUE_INFORMATION_REQUEST: |
472 | 0 | option = fr_dhcpv6_option_find(options, end, FR_SERVER_ID); |
473 | 0 | if (!option) goto fail_sid; |
474 | | |
475 | 0 | if (!duid_match(option, packet_ctx)) goto fail_match; |
476 | | |
477 | | /* |
478 | | * IA options are forbidden. |
479 | | */ |
480 | 0 | if (fr_dhcpv6_option_find(options, end, FR_IA_NA)) { |
481 | 0 | fr_strerror_const("Packet contains an IA-NA option"); |
482 | 0 | return false; |
483 | 0 | } |
484 | 0 | if (fr_dhcpv6_option_find(options, end, FR_IA_TA)) { |
485 | 0 | fr_strerror_const("Packet contains an IA-TA option"); |
486 | 0 | return false; |
487 | 0 | } |
488 | 0 | if (fr_dhcpv6_option_find(options, end, FR_IA_ADDR)) { |
489 | 0 | fr_strerror_const("Packet contains an IA-Addr option"); |
490 | 0 | return false; |
491 | 0 | } |
492 | 0 | break; |
493 | | |
494 | 0 | case FR_DHCPV6_RELAY_FORWARD: |
495 | 0 | if (packet_len < DHCPV6_RELAY_HDR_LEN) { |
496 | 0 | fr_strerror_const("Relay-Forward message is too small"); |
497 | 0 | return false; |
498 | 0 | } |
499 | | |
500 | 0 | options += (DHCPV6_RELAY_HDR_LEN - 4); /* we assumed it was a normal packet above */ |
501 | 0 | option = fr_dhcpv6_option_find(options, end, FR_RELAY_MESSAGE); |
502 | 0 | if (!option) { |
503 | 0 | fr_strerror_const("Packet does not contain a Relay-Message option"); |
504 | 0 | return false; |
505 | 0 | } |
506 | | |
507 | 0 | return verify_from_client(option + 4, DHCPV6_GET_OPTION_LEN(option), packet_ctx); |
508 | | |
509 | 0 | case FR_PACKET_TYPE_VALUE_LEASE_QUERY: |
510 | 0 | if (!fr_dhcpv6_option_find(options, end, FR_CLIENT_ID)) goto fail_cid; |
511 | | |
512 | | /* |
513 | | * Server-ID is a SHOULD, but if it exists, it |
514 | | * MUST match. |
515 | | */ |
516 | 0 | option = fr_dhcpv6_option_find(options, end, FR_SERVER_ID); |
517 | 0 | if (option && !duid_match(option, packet_ctx)) goto fail_match; |
518 | | |
519 | 0 | option = fr_dhcpv6_option_find(options, end, FR_LEASE_QUERY); |
520 | 0 | if (!option) { |
521 | 0 | fr_strerror_const("Packet does not contain a Lease-Query option"); |
522 | 0 | return false; |
523 | 0 | } |
524 | 0 | break; |
525 | | |
526 | 0 | case FR_PACKET_TYPE_VALUE_ADVERTISE: |
527 | 0 | case FR_PACKET_TYPE_VALUE_REPLY: |
528 | 0 | case FR_PACKET_TYPE_VALUE_RECONFIGURE: |
529 | 0 | default: |
530 | 0 | fr_strerror_const("Invalid message type sent to server"); |
531 | 0 | return false; |
532 | 0 | } |
533 | 0 | return true; |
534 | 0 | } |
535 | | |
536 | | /** Verify the packet under some various circumstances |
537 | | * |
538 | | * @param[in] packet to check. |
539 | | * @param[in] packet_len The size of the packet data. |
540 | | * @param[in] packet_ctx The expected packet_ctx |
541 | | * @param[in] from_server true for packets from a server, false for packets from a client. |
542 | | * @return |
543 | | * - True on success. |
544 | | * - False on failure. |
545 | | * |
546 | | * fr_dhcpv6_ok() SHOULD be called before calling this function. |
547 | | */ |
548 | | bool fr_dhcpv6_verify(uint8_t const *packet, size_t packet_len, fr_dhcpv6_decode_ctx_t const *packet_ctx, |
549 | | bool from_server) |
550 | 0 | { |
551 | 0 | if (packet_len < DHCPV6_HDR_LEN) return false; |
552 | | |
553 | | /* |
554 | | * We support up to relaying. |
555 | | */ |
556 | 0 | if ((packet[0] == 0) || (packet[0] > FR_PACKET_TYPE_VALUE_RELAY_REPLY)) return false; |
557 | | |
558 | 0 | if (!packet_ctx->duid) return false; |
559 | | |
560 | 0 | if (from_server) return verify_to_client(packet, packet_len, packet_ctx); |
561 | | |
562 | 0 | return verify_from_client(packet, packet_len, packet_ctx); |
563 | 0 | } |
564 | | |
565 | | /* |
566 | | |
567 | | 0 1 2 3 |
568 | | 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 |
569 | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
570 | | | msg-type | transaction-id | |
571 | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
572 | | | | |
573 | | . options . |
574 | | . (variable number and length) . |
575 | | | | |
576 | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
577 | | */ |
578 | | |
579 | | /** Decode a DHCPv6 packet |
580 | | * |
581 | | */ |
582 | | ssize_t fr_dhcpv6_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *packet, size_t packet_len) |
583 | 1.71k | { |
584 | 1.71k | ssize_t slen = -1; |
585 | 1.71k | uint8_t const *p, *end; |
586 | 1.71k | fr_dhcpv6_decode_ctx_t packet_ctx = {}; |
587 | 1.71k | fr_pair_t *vp; |
588 | 1.71k | fr_pair_list_t tmp; |
589 | | |
590 | 1.71k | if (packet_len < DHCPV6_HDR_LEN) return 0; /* protect access to packet[0] */ |
591 | | |
592 | | /* |
593 | | * Get the packet type. |
594 | | */ |
595 | 1.57k | vp = fr_pair_afrom_da(ctx, attr_packet_type); |
596 | 1.57k | if (!vp) return -1; |
597 | | |
598 | 1.57k | fr_pair_list_init(&tmp); |
599 | 1.57k | vp->vp_uint32 = packet[0]; |
600 | 1.57k | fr_pair_append(&tmp, vp); |
601 | | |
602 | 1.57k | switch (packet[0]) { |
603 | 587 | case FR_DHCPV6_RELAY_FORWARD: |
604 | 817 | case FR_DHCPV6_RELAY_REPLY: |
605 | | /* |
606 | | * Just for sanity check. |
607 | | */ |
608 | 817 | if (packet_len < DHCPV6_RELAY_HDR_LEN) return -1; |
609 | | |
610 | | /* |
611 | | * Decode the header fields. |
612 | | */ |
613 | 352 | vp = fr_pair_afrom_da(ctx, attr_hop_count); |
614 | 352 | if (!vp) goto fail; |
615 | 352 | if (fr_value_box_from_network(vp, &vp->data, vp->vp_type, NULL, |
616 | 352 | &FR_DBUFF_TMP(packet + 1, 1), 1, true) < 0) { |
617 | 0 | goto fail; |
618 | 0 | } |
619 | 352 | fr_pair_append(&tmp, vp); |
620 | | |
621 | 352 | vp = fr_pair_afrom_da(ctx, attr_relay_link_address); |
622 | 352 | if (!vp) goto fail; |
623 | 352 | if (fr_value_box_from_network(vp, &vp->data, vp->vp_type, NULL, |
624 | 352 | &FR_DBUFF_TMP(packet + 2, 16), 16, true) < 0) { |
625 | 0 | goto fail; |
626 | 0 | } |
627 | 352 | fr_pair_append(&tmp, vp); |
628 | | |
629 | 352 | vp = fr_pair_afrom_da(ctx, attr_relay_peer_address); |
630 | 352 | if (!vp) goto fail; |
631 | 352 | if (fr_value_box_from_network(vp, &vp->data, vp->vp_type, NULL, |
632 | 352 | &FR_DBUFF_TMP(packet + 2 + 16, 16), 16, true) < 0) { |
633 | 0 | goto fail; |
634 | 0 | } |
635 | | |
636 | 352 | fr_pair_append(&tmp, vp); |
637 | | |
638 | 352 | p = packet + DHCPV6_RELAY_HDR_LEN; |
639 | 352 | goto decode_options; |
640 | | |
641 | 757 | default: |
642 | 757 | break; |
643 | 1.57k | } |
644 | | |
645 | | /* |
646 | | * And the transaction ID. |
647 | | */ |
648 | 757 | vp = fr_pair_afrom_da(ctx, attr_transaction_id); |
649 | 757 | if (!vp) { |
650 | 631 | fail: |
651 | 631 | fr_pair_list_free(&tmp); |
652 | 631 | return slen; |
653 | 0 | } |
654 | | |
655 | | /* |
656 | | * Copy 3 octets over. |
657 | | */ |
658 | 757 | (void) fr_pair_value_memdup(vp, packet + 1, 3, false); |
659 | | |
660 | 757 | fr_pair_append(&tmp, vp); |
661 | | |
662 | 757 | p = packet + 4; |
663 | | |
664 | 1.10k | decode_options: |
665 | 1.10k | end = packet + packet_len; |
666 | 1.10k | packet_ctx.tmp_ctx = talloc_init_const("tmp"); |
667 | | |
668 | | /* |
669 | | * The caller MUST have called fr_dhcpv6_ok() first. If |
670 | | * he doesn't, all hell breaks loose. |
671 | | */ |
672 | 2.75k | while (p < end) { |
673 | 2.27k | slen = fr_dhcpv6_decode_option(ctx, &tmp, p, (end - p), &packet_ctx); |
674 | 2.27k | if (slen < 0) { |
675 | 631 | talloc_free(packet_ctx.tmp_ctx); |
676 | 631 | goto fail; |
677 | 631 | } |
678 | | /* |
679 | | * If slen is larger than the room in the packet, |
680 | | * all kinds of bad things happen. |
681 | | */ |
682 | 1.64k | if (!fr_cond_assert(slen <= (end - p))) { |
683 | 0 | talloc_free(packet_ctx.tmp_ctx); |
684 | 0 | goto fail; |
685 | 0 | } |
686 | | |
687 | 1.64k | p += slen; |
688 | 1.64k | talloc_free_children(packet_ctx.tmp_ctx); |
689 | 1.64k | } |
690 | 478 | fr_pair_list_append(out, &tmp); |
691 | | |
692 | | /* |
693 | | * We've parsed the whole packet, return that. |
694 | | */ |
695 | 478 | talloc_free(packet_ctx.tmp_ctx); |
696 | 478 | return packet_len; |
697 | 1.10k | } |
698 | | |
699 | | /** DHCPV6-specific iterator |
700 | | * |
701 | | */ |
702 | | void *fr_dhcpv6_next_encodable(fr_dcursor_t *cursor, void *current, void *uctx) |
703 | 0 | { |
704 | 0 | fr_pair_t *c = current; |
705 | 0 | fr_dict_t *dict = talloc_get_type_abort(uctx, fr_dict_t); |
706 | |
|
707 | 0 | while ((c = fr_dlist_next(cursor->dlist, c))) { |
708 | 0 | PAIR_VERIFY(c); |
709 | 0 | if (c->da->dict != dict || c->da->flags.internal) continue; |
710 | 0 | if (c->vp_type == FR_TYPE_BOOL && !c->vp_bool) continue; |
711 | | |
712 | 0 | break; |
713 | 0 | } |
714 | |
|
715 | 0 | return c; |
716 | 0 | } |
717 | | |
718 | | /** Encode a DHCPv6 packet |
719 | | * |
720 | | */ |
721 | | ssize_t fr_dhcpv6_encode(fr_dbuff_t *dbuff, uint8_t const *original, size_t length, int msg_type, fr_pair_list_t *vps) |
722 | 0 | { |
723 | 0 | fr_dbuff_t frame_dbuff = FR_DBUFF(dbuff); |
724 | 0 | fr_pair_t *vp; |
725 | 0 | fr_dict_attr_t const *root; |
726 | 0 | ssize_t slen; |
727 | 0 | fr_dcursor_t cursor; |
728 | 0 | fr_dhcpv6_encode_ctx_t packet_ctx; |
729 | |
|
730 | 0 | root = fr_dict_root(dict_dhcpv6); |
731 | |
|
732 | 0 | if (!msg_type) { |
733 | 0 | vp = fr_pair_find_by_da(vps, NULL, attr_packet_type); |
734 | 0 | if (vp) msg_type = vp->vp_uint32; |
735 | 0 | } |
736 | |
|
737 | 0 | if ((msg_type <= 0) || (msg_type > UINT8_MAX)) { |
738 | 0 | fr_strerror_printf("Invalid message type %d", msg_type); |
739 | 0 | return -1; |
740 | 0 | } |
741 | | |
742 | 0 | FR_DBUFF_IN_RETURN(&frame_dbuff, (uint8_t)msg_type); |
743 | | |
744 | 0 | switch (msg_type) { |
745 | 0 | case FR_DHCPV6_RELAY_REPLY: |
746 | 0 | case FR_DHCPV6_RELAY_FORWARD: |
747 | 0 | vp = fr_pair_find_by_da(vps, NULL, attr_hop_count); |
748 | 0 | if (likely(vp != NULL)) { |
749 | 0 | FR_VALUE_BOX_TO_NETWORK_RETURN(&frame_dbuff, &vp->data); |
750 | 0 | } else { |
751 | 0 | FR_DBUFF_MEMSET_RETURN(&frame_dbuff, 0, DHCPV6_HOP_COUNT_LEN); |
752 | 0 | } |
753 | | |
754 | 0 | vp = fr_pair_find_by_da(vps, NULL, attr_relay_link_address); |
755 | 0 | if (likely(vp != NULL)) { |
756 | 0 | FR_VALUE_BOX_TO_NETWORK_RETURN(&frame_dbuff, &vp->data); |
757 | 0 | } else { |
758 | 0 | FR_DBUFF_MEMSET_RETURN(&frame_dbuff, 0, DHCPV6_LINK_ADDRESS_LEN); |
759 | 0 | } |
760 | | |
761 | 0 | vp = fr_pair_find_by_da(vps, NULL, attr_relay_peer_address); |
762 | 0 | if (likely(vp != NULL)) { |
763 | 0 | FR_VALUE_BOX_TO_NETWORK_RETURN(&frame_dbuff, &vp->data); |
764 | 0 | } else { |
765 | 0 | FR_DBUFF_MEMSET_RETURN(&frame_dbuff, 0, DHCPV6_PEER_ADDRESS_LEN); |
766 | 0 | } |
767 | 0 | break; |
768 | | |
769 | 0 | default: |
770 | | /* |
771 | | * We can set an XID, or we can pick a random one. |
772 | | */ |
773 | 0 | vp = fr_pair_find_by_da(vps, NULL, attr_transaction_id); |
774 | 0 | if (vp && (vp->vp_length >= DHCPV6_TRANSACTION_ID_LEN)) { |
775 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&frame_dbuff, vp->vp_octets, DHCPV6_TRANSACTION_ID_LEN); |
776 | 0 | } else { |
777 | 0 | uint8_t id[DHCPV6_TRANSACTION_ID_LEN]; |
778 | 0 | fr_nbo_from_uint24(id, fr_rand()); |
779 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&frame_dbuff, id, sizeof(id)); /* Need 24 bits of the 32bit integer */ |
780 | 0 | } |
781 | 0 | break; |
782 | 0 | } |
783 | | |
784 | | /* |
785 | | * Encode options. |
786 | | */ |
787 | 0 | packet_ctx.root = root; |
788 | 0 | packet_ctx.original = original; |
789 | 0 | packet_ctx.original_length = length; |
790 | |
|
791 | 0 | fr_pair_dcursor_iter_init(&cursor, vps, fr_dhcpv6_next_encodable, dict_dhcpv6); |
792 | 0 | while ((fr_dbuff_extend(&frame_dbuff) > 0) && (fr_dcursor_current(&cursor) != NULL)) { |
793 | 0 | slen = fr_dhcpv6_encode_option(&frame_dbuff, &cursor, &packet_ctx); |
794 | 0 | if (slen < 0) return FR_DBUFF_ERROR_OFFSET(slen, fr_dbuff_used(&frame_dbuff)); |
795 | 0 | } |
796 | | |
797 | 0 | return fr_dbuff_set(dbuff, &frame_dbuff); |
798 | 0 | } |
799 | | |
800 | | |
801 | | static char const tabs[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; |
802 | | |
803 | | static void print_hex_data(FILE *fp, uint8_t const *ptr, int attrlen, int depth) |
804 | 0 | { |
805 | 0 | int i; |
806 | |
|
807 | 0 | for (i = 0; i < attrlen; i++) { |
808 | 0 | if ((i > 0) && ((i & 0x0f) == 0x00)) |
809 | 0 | fprintf(fp, "%.*s", depth, tabs); |
810 | 0 | fprintf(fp, "%02x ", ptr[i]); |
811 | 0 | if ((i & 0x0f) == 0x0f) fprintf(fp, "\n"); |
812 | 0 | } |
813 | 0 | if ((i & 0x0f) != 0) fprintf(fp, "\n"); |
814 | 0 | } |
815 | | |
816 | | static void dhcpv6_print_hex(FILE *fp, uint8_t const *packet, size_t packet_len, int depth) |
817 | 0 | { |
818 | 0 | uint8_t const *option, *end = packet + packet_len; |
819 | |
|
820 | 0 | if (packet_len < 4) { |
821 | 0 | fprintf(fp, "%.*s", depth, tabs); |
822 | 0 | fprintf(fp, "???:\t"); |
823 | 0 | print_hex_data(fp, packet, packet_len, depth + 1); |
824 | 0 | return; |
825 | 0 | } |
826 | | |
827 | 0 | fprintf(fp, "%.*s", depth, tabs); |
828 | 0 | if ((packet[0] > 0) && (packet[0] < FR_DHCPV6_CODE_MAX)) { |
829 | 0 | fprintf(fp, "packet: %s\n", fr_dhcpv6_packet_names[packet[0]]); |
830 | 0 | } else { |
831 | 0 | fprintf(fp, "packet: %02x\n", packet[0]); |
832 | 0 | } |
833 | |
|
834 | 0 | if ((packet[0] == FR_PACKET_TYPE_VALUE_RELAY_FORWARD) || |
835 | 0 | (packet[0] == FR_PACKET_TYPE_VALUE_RELAY_REPLY)) { |
836 | 0 | if (packet_len < 34) { |
837 | 0 | fprintf(fp, "%.*s", depth, tabs); |
838 | 0 | fprintf(fp, "???:\t"); |
839 | 0 | print_hex_data(fp, packet + 1, packet_len - 1, depth + 1); |
840 | 0 | return; |
841 | 0 | } |
842 | | |
843 | 0 | fprintf(fp, "%.*s", depth, tabs); |
844 | 0 | fprintf(fp, "hops: %02x\n", packet[1]); |
845 | 0 | fprintf(fp, "%.*s", depth, tabs); |
846 | 0 | fprintf(fp, "relay link address: "); |
847 | 0 | print_hex_data(fp, packet + 2, 16, depth + 1); |
848 | |
|
849 | 0 | fprintf(fp, "%.*s", depth, tabs); |
850 | 0 | fprintf(fp, "peer address: "); |
851 | 0 | print_hex_data(fp, packet + 18, 16, depth + 1); |
852 | 0 | option = packet + 34; |
853 | 0 | } else { |
854 | 0 | fprintf(fp, "%.*s", depth, tabs); |
855 | 0 | fprintf(fp, "transaction id: "); |
856 | 0 | print_hex_data(fp, packet + 1, 3, depth + 1); |
857 | 0 | option = packet + 4; |
858 | 0 | } |
859 | | |
860 | 0 | fprintf(fp, "%.*s", depth, tabs); |
861 | 0 | fprintf(fp, "options\n"); |
862 | 0 | while (option < end) { |
863 | 0 | uint16_t length; |
864 | |
|
865 | 0 | if ((end - option) < 4) { |
866 | 0 | fprintf(fp, "%.*s", depth + 1, tabs); |
867 | 0 | fprintf(fp, "???:\t"); |
868 | 0 | print_hex_data(fp, option, end - option, depth + 2); |
869 | 0 | break; |
870 | 0 | } |
871 | | |
872 | 0 | length = fr_nbo_to_uint16(option + 2); |
873 | 0 | fprintf(fp, "%.*s", depth + 1, tabs); |
874 | 0 | fprintf(fp, "%04x %04x\t", fr_nbo_to_uint16(option), length); |
875 | |
|
876 | 0 | if (length > end - (option + 4)) { |
877 | 0 | print_hex_data(fp, option + 4, end - (option + 4), depth + 3); |
878 | 0 | break; |
879 | 0 | } |
880 | | |
881 | 0 | print_hex_data(fp, option + 4, length, depth + 3); |
882 | 0 | if ((option[0] == 0) && (option[1] == attr_relay_message->attr)) { |
883 | 0 | dhcpv6_print_hex(fp, option + 4, length, depth + 2); |
884 | 0 | } |
885 | |
|
886 | 0 | option += 4 + length; |
887 | 0 | } |
888 | |
|
889 | 0 | fprintf(fp, "\n"); |
890 | 0 | } |
891 | | |
892 | | /** Print a raw DHCP packet as hex. |
893 | | * |
894 | | */ |
895 | | void fr_dhcpv6_print_hex(FILE *fp, uint8_t const *packet, size_t packet_len) |
896 | 0 | { |
897 | 0 | dhcpv6_print_hex(fp, packet, packet_len, 0); |
898 | 0 | } |
899 | | |
900 | | int fr_dhcpv6_global_init(void) |
901 | 2 | { |
902 | 2 | if (instance_count > 0) { |
903 | 0 | instance_count++; |
904 | 0 | return 0; |
905 | 0 | } |
906 | | |
907 | 2 | instance_count++; |
908 | | |
909 | 2 | if (fr_dict_autoload(libfreeradius_dhcpv6_dict) < 0) { |
910 | 0 | fail: |
911 | 0 | instance_count--; |
912 | 0 | return -1; |
913 | 0 | } |
914 | | |
915 | 2 | if (fr_dict_attr_autoload(libfreeradius_dhcpv6_dict_attr) < 0) { |
916 | 0 | fr_dict_autofree(libfreeradius_dhcpv6_dict); |
917 | 0 | goto fail; |
918 | 0 | } |
919 | | |
920 | 2 | instantiated = true; |
921 | 2 | return 0; |
922 | 2 | } |
923 | | |
924 | | void fr_dhcpv6_global_free(void) |
925 | 0 | { |
926 | 0 | if (!instantiated) return; |
927 | | |
928 | 0 | fr_assert(instance_count > 0); |
929 | |
|
930 | 0 | if (--instance_count > 0) return; |
931 | | |
932 | 0 | fr_dict_autofree(libfreeradius_dhcpv6_dict); |
933 | 0 | instantiated = false; |
934 | 0 | } |
935 | | |
936 | | static bool attr_valid(fr_dict_attr_t *da) |
937 | 762 | { |
938 | | /* |
939 | | * DNS labels are strings, but are known width. |
940 | | */ |
941 | 762 | if (fr_dhcpv6_flag_any_dns_label(da)) { |
942 | 40 | if (da->type != FR_TYPE_STRING) { |
943 | 0 | fr_strerror_const("The 'dns_label' flag can only be used with attributes of type 'string'"); |
944 | 0 | return false; |
945 | 0 | } |
946 | | |
947 | 40 | da->flags.is_known_width = true; |
948 | 40 | da->flags.length = 0; |
949 | 40 | } |
950 | | |
951 | 762 | if (da->type == FR_TYPE_ATTR) { |
952 | 2 | da->flags.is_known_width = true; |
953 | 2 | da->flags.length = 2; |
954 | 2 | } |
955 | | |
956 | 762 | if (da_is_length_field8(da)) { |
957 | 0 | fr_strerror_const("The 'length=uint8' flag cannot be used for DHCPv6"); |
958 | 0 | return false; |
959 | 0 | } |
960 | | |
961 | | /* |
962 | | * "arrays" of string/octets are encoded as a 16-bit |
963 | | * length, followed by the actual data. |
964 | | */ |
965 | 762 | if (da->flags.array) { |
966 | 56 | if ((da->type == FR_TYPE_STRING) || (da->type == FR_TYPE_OCTETS)) { |
967 | 22 | if (da->flags.extra && !da_is_length_field16(da)) { |
968 | 0 | fr_strerror_const("Invalid flags"); |
969 | 0 | return false; |
970 | 0 | } |
971 | | |
972 | 22 | da->flags.is_known_width = true; |
973 | 22 | da->flags.extra = true; |
974 | 22 | da->flags.subtype = FLAG_LENGTH_UINT16; |
975 | 22 | } |
976 | | |
977 | 56 | if (!da->flags.is_known_width) { |
978 | 0 | fr_strerror_const("DHCPv6 arrays require data types which have known width"); |
979 | 0 | return false; |
980 | 0 | } |
981 | 56 | } |
982 | | |
983 | | /* |
984 | | * "extra" signifies that subtype is being used by the |
985 | | * dictionaries itself. |
986 | | */ |
987 | 762 | if (da->flags.extra || !da->flags.subtype) return true; |
988 | | |
989 | 0 | if ((da->type == FR_TYPE_ATTR) && !da->parent->flags.is_root) { |
990 | 0 | fr_strerror_const("The 'attribute' data type can only be used at the dictionary root"); |
991 | 0 | return false; |
992 | 0 | } |
993 | | |
994 | 0 | da->flags.is_known_width = true; |
995 | |
|
996 | 0 | return true; |
997 | 0 | } |
998 | | |
999 | | extern fr_dict_protocol_t libfreeradius_dhcpv6_dict_protocol; |
1000 | | fr_dict_protocol_t libfreeradius_dhcpv6_dict_protocol = { |
1001 | | .name = "dhcpv6", |
1002 | | .default_type_size = 2, |
1003 | | .default_type_length = 2, |
1004 | | |
1005 | | .attr = { |
1006 | | .valid = attr_valid, |
1007 | | .flags = { |
1008 | | .table = dhcpv6_flags, |
1009 | | .table_len = NUM_ELEMENTS(dhcpv6_flags), |
1010 | | .len = sizeof(fr_dhcpv6_attr_flags_t) |
1011 | | } |
1012 | | }, |
1013 | | |
1014 | | .init = fr_dhcpv6_global_init, |
1015 | | .free = fr_dhcpv6_global_free, |
1016 | | |
1017 | | .encode = fr_dhcpv6_encode_foreign, |
1018 | | .decode = fr_dhcpv6_decode_foreign, |
1019 | | }; |