]>
Commit | Line | Data |
---|---|---|
a0245818 SE |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * Copyright (C) Microsoft Corporation | |
4 | * Author: Sean Edmond <seanedmond@microsoft.com> | |
5 | * | |
6 | */ | |
7 | ||
8 | /* Simple DHCP6 network layer implementation. */ | |
9 | ||
10 | #include <common.h> | |
11 | #include <net6.h> | |
12 | #include <malloc.h> | |
13 | #include <linux/delay.h> | |
14 | #include "net_rand.h" | |
15 | #include "dhcpv6.h" | |
16 | ||
17 | #define PORT_DHCP6_S 547 /* DHCP6 server UDP port */ | |
18 | #define PORT_DHCP6_C 546 /* DHCP6 client UDP port */ | |
19 | ||
20 | /* default timeout parameters (in ms) */ | |
21 | #define SOL_MAX_DELAY_MS 1000 | |
22 | #define SOL_TIMEOUT_MS 1000 | |
23 | #define SOL_MAX_RT_MS 3600000 | |
24 | #define REQ_TIMEOUT_MS 1000 | |
25 | #define REQ_MAX_RT_MS 30000 | |
26 | #define REQ_MAX_RC 10 | |
27 | #define MAX_WAIT_TIME_MS 60000 | |
28 | ||
29 | /* global variable to track any updates from DHCP6 server */ | |
30 | int updated_sol_max_rt_ms = SOL_MAX_RT_MS; | |
31 | /* state machine parameters/variables */ | |
32 | struct dhcp6_sm_params sm_params; | |
33 | ||
34 | static void dhcp6_state_machine(bool timeout, uchar *rx_pkt, unsigned int len); | |
35 | ||
36 | /* Handle DHCP received packets (set as UDP handler) */ | |
37 | static void dhcp6_handler(uchar *pkt, unsigned int dest, struct in_addr sip, | |
38 | unsigned int src, unsigned int len) | |
39 | { | |
40 | /* return if ports don't match DHCPv6 ports */ | |
41 | if (dest != PORT_DHCP6_C || src != PORT_DHCP6_S) | |
42 | return; | |
43 | ||
44 | dhcp6_state_machine(false, pkt, len); | |
45 | } | |
46 | ||
47 | /** | |
48 | * dhcp6_add_option() - Adds DHCP6 option to a packet | |
49 | * @option_id: The option ID to add (See DHCP6_OPTION_* definitions) | |
50 | * @pkt: A pointer to the current write location of the TX packet | |
51 | * | |
52 | * Return: The number of bytes written into "*pkt" | |
53 | */ | |
54 | static int dhcp6_add_option(int option_id, uchar *pkt) | |
55 | { | |
56 | struct dhcp6_option_duid_ll *duid_opt; | |
57 | struct dhcp6_option_elapsed_time *elapsed_time_opt; | |
58 | struct dhcp6_option_ia_ta *ia_ta_opt; | |
59 | struct dhcp6_option_ia_na *ia_na_opt; | |
60 | struct dhcp6_option_oro *oro_opt; | |
61 | struct dhcp6_option_client_arch *client_arch_opt; | |
62 | struct dhcp6_option_vendor_class *vendor_class_opt; | |
63 | int opt_len; | |
64 | long elapsed_time; | |
65 | size_t vci_strlen; | |
66 | int num_oro = 0; | |
67 | int num_client_arch = 0; | |
68 | int num_vc_data = 0; | |
69 | struct dhcp6_option_hdr *dhcp_option = (struct dhcp6_option_hdr *)pkt; | |
70 | uchar *dhcp_option_start = pkt + sizeof(struct dhcp6_option_hdr); | |
71 | ||
72 | dhcp_option->option_id = htons(option_id); | |
73 | ||
74 | switch (option_id) { | |
75 | case DHCP6_OPTION_CLIENTID: | |
76 | /* Only support for DUID-LL in Client ID option for now */ | |
77 | duid_opt = (struct dhcp6_option_duid_ll *)dhcp_option_start; | |
78 | duid_opt->duid_type = htons(DUID_TYPE_LL); | |
79 | duid_opt->hw_type = htons(DUID_HW_TYPE_ENET); | |
80 | memcpy(duid_opt->ll_addr, net_ethaddr, ETH_ALEN); | |
81 | opt_len = sizeof(struct dhcp6_option_duid_ll) + ETH_ALEN; | |
82 | ||
83 | /* Save DUID for comparison later */ | |
84 | memcpy(sm_params.duid, duid_opt, opt_len); | |
85 | break; | |
86 | case DHCP6_OPTION_ELAPSED_TIME: | |
87 | /* calculate elapsed time in 1/100th of a second */ | |
88 | elapsed_time = (sm_params.dhcp6_retry_ms - | |
89 | sm_params.dhcp6_start_ms) / 10; | |
90 | if (elapsed_time > 0xFFFF) | |
91 | elapsed_time = 0xFFFF; | |
92 | ||
93 | elapsed_time_opt = (struct dhcp6_option_elapsed_time *)dhcp_option_start; | |
94 | elapsed_time_opt->elapsed_time = htons(elapsed_time); | |
95 | ||
96 | opt_len = sizeof(struct dhcp6_option_elapsed_time); | |
97 | break; | |
98 | case DHCP6_OPTION_IA_TA: | |
99 | ia_ta_opt = (struct dhcp6_option_ia_ta *)dhcp_option_start; | |
100 | ia_ta_opt->iaid = htonl(sm_params.ia_id); | |
101 | ||
102 | opt_len = sizeof(struct dhcp6_option_ia_ta); | |
103 | break; | |
104 | case DHCP6_OPTION_IA_NA: | |
105 | ia_na_opt = (struct dhcp6_option_ia_na *)dhcp_option_start; | |
106 | ia_na_opt->iaid = htonl(sm_params.ia_id); | |
107 | /* In a message sent by a client to a server, | |
108 | * the T1 and T2 fields SHOULD be set to 0 | |
109 | */ | |
110 | ia_na_opt->t1 = 0; | |
111 | ia_na_opt->t2 = 0; | |
112 | ||
113 | opt_len = sizeof(struct dhcp6_option_ia_na); | |
114 | break; | |
115 | case DHCP6_OPTION_ORO: | |
116 | oro_opt = (struct dhcp6_option_oro *)dhcp_option_start; | |
117 | oro_opt->req_option_code[num_oro++] = htons(DHCP6_OPTION_OPT_BOOTFILE_URL); | |
118 | oro_opt->req_option_code[num_oro++] = htons(DHCP6_OPTION_SOL_MAX_RT); | |
119 | if (IS_ENABLED(CONFIG_DHCP6_PXE_DHCP_OPTION)) { | |
120 | oro_opt->req_option_code[num_oro++] = | |
121 | htons(DHCP6_OPTION_OPT_BOOTFILE_PARAM); | |
122 | } | |
123 | ||
124 | opt_len = sizeof(__be16) * num_oro; | |
125 | break; | |
126 | case DHCP6_OPTION_CLIENT_ARCH_TYPE: | |
127 | client_arch_opt = (struct dhcp6_option_client_arch *)dhcp_option_start; | |
128 | client_arch_opt->arch_type[num_client_arch++] = htons(CONFIG_DHCP6_PXE_CLIENTARCH); | |
129 | ||
130 | opt_len = sizeof(__be16) * num_client_arch; | |
131 | break; | |
132 | case DHCP6_OPTION_VENDOR_CLASS: | |
133 | vendor_class_opt = (struct dhcp6_option_vendor_class *)dhcp_option_start; | |
134 | vendor_class_opt->enterprise_number = htonl(CONFIG_DHCP6_ENTERPRISE_ID); | |
135 | ||
136 | vci_strlen = strlen(DHCP6_VCI_STRING); | |
137 | vendor_class_opt->vendor_class_data[num_vc_data].vendor_class_len = | |
138 | htons(vci_strlen); | |
139 | memcpy(vendor_class_opt->vendor_class_data[num_vc_data].opaque_data, | |
140 | DHCP6_VCI_STRING, vci_strlen); | |
141 | num_vc_data++; | |
142 | ||
143 | opt_len = sizeof(struct dhcp6_option_vendor_class) + | |
144 | sizeof(struct vendor_class_data) * num_vc_data + | |
145 | vci_strlen; | |
146 | break; | |
147 | case DHCP6_OPTION_NII: | |
148 | dhcp_option_start[0] = 1; | |
149 | dhcp_option_start[1] = 0; | |
150 | dhcp_option_start[2] = 0; | |
151 | ||
152 | opt_len = 3; | |
153 | break; | |
154 | default: | |
155 | printf("***Warning unknown DHCP6 option %d. Not adding to message\n", option_id); | |
156 | return 0; | |
157 | } | |
158 | dhcp_option->option_len = htons(opt_len); | |
159 | ||
160 | return opt_len + sizeof(struct dhcp6_option_hdr); | |
161 | } | |
162 | ||
163 | /** | |
164 | * dhcp6_send_solicit_packet() - Send a SOLICIT packet | |
165 | * | |
166 | * Implements RFC 8415: | |
167 | * - 16.2. Solicit Message | |
168 | * - 18.2.1. Creation and Transmission of Solicit Messages | |
169 | * | |
170 | * Adds DHCP6 header and DHCP6 options. Sends the UDP packet | |
171 | * and sets the UDP handler. | |
172 | */ | |
173 | static void dhcp6_send_solicit_packet(void) | |
174 | { | |
175 | struct in6_addr dhcp_bcast_ip6; | |
176 | int len = 0; | |
177 | uchar *pkt; | |
178 | uchar *dhcp_pkt_start_ptr; | |
179 | struct dhcp6_hdr *dhcp_hdr; | |
180 | ||
181 | pkt = net_tx_packet + net_eth_hdr_size() + IP6_HDR_SIZE + UDP_HDR_SIZE; | |
182 | dhcp_pkt_start_ptr = pkt; | |
183 | ||
184 | /* Add the DHCP6 header */ | |
185 | dhcp_hdr = (struct dhcp6_hdr *)pkt; | |
186 | dhcp_hdr->msg_type = DHCP6_MSG_SOLICIT; | |
187 | dhcp_hdr->trans_id = htons(sm_params.trans_id); | |
188 | pkt += sizeof(struct dhcp6_hdr); | |
189 | ||
190 | /* Add the options */ | |
191 | pkt += dhcp6_add_option(DHCP6_OPTION_CLIENTID, pkt); | |
192 | pkt += dhcp6_add_option(DHCP6_OPTION_ELAPSED_TIME, pkt); | |
193 | pkt += dhcp6_add_option(DHCP6_OPTION_IA_NA, pkt); | |
194 | pkt += dhcp6_add_option(DHCP6_OPTION_ORO, pkt); | |
195 | if (CONFIG_DHCP6_PXE_CLIENTARCH != 0xFF) | |
196 | pkt += dhcp6_add_option(DHCP6_OPTION_CLIENT_ARCH_TYPE, pkt); | |
197 | pkt += dhcp6_add_option(DHCP6_OPTION_VENDOR_CLASS, pkt); | |
198 | pkt += dhcp6_add_option(DHCP6_OPTION_NII, pkt); | |
199 | ||
200 | /* calculate packet length */ | |
201 | len = pkt - dhcp_pkt_start_ptr; | |
202 | ||
203 | /* send UDP packet to DHCP6 multicast address */ | |
204 | string_to_ip6(DHCP6_MULTICAST_ADDR, sizeof(DHCP6_MULTICAST_ADDR), &dhcp_bcast_ip6); | |
205 | net_set_udp_handler(dhcp6_handler); | |
206 | net_send_udp_packet6((uchar *)net_bcast_ethaddr, &dhcp_bcast_ip6, | |
207 | PORT_DHCP6_S, PORT_DHCP6_C, len); | |
208 | } | |
209 | ||
210 | /** | |
211 | * dhcp6_send_request_packet() - Send a REQUEST packet | |
212 | * | |
213 | * * Implements RFC 8415: | |
214 | * - 16.4. Request Message | |
215 | * - 18.2.2. Creation and Transmission of Request Messages | |
216 | * | |
217 | * Adds DHCP6 header and DHCP6 options. Sends the UDP packet | |
218 | * and sets the UDP handler. | |
219 | */ | |
220 | static void dhcp6_send_request_packet(void) | |
221 | { | |
222 | struct in6_addr dhcp_bcast_ip6; | |
223 | int len = 0; | |
224 | uchar *pkt; | |
225 | uchar *dhcp_pkt_start_ptr; | |
226 | struct dhcp6_hdr *dhcp_hdr; | |
227 | ||
228 | pkt = net_tx_packet + net_eth_hdr_size() + IP6_HDR_SIZE + UDP_HDR_SIZE; | |
229 | dhcp_pkt_start_ptr = pkt; | |
230 | ||
231 | /* Add the DHCP6 header */ | |
232 | dhcp_hdr = (struct dhcp6_hdr *)pkt; | |
233 | dhcp_hdr->msg_type = DHCP6_MSG_REQUEST; | |
234 | dhcp_hdr->trans_id = htons(sm_params.trans_id); | |
235 | pkt += sizeof(struct dhcp6_hdr); | |
236 | ||
237 | /* add the options */ | |
238 | pkt += dhcp6_add_option(DHCP6_OPTION_CLIENTID, pkt); | |
239 | pkt += dhcp6_add_option(DHCP6_OPTION_ELAPSED_TIME, pkt); | |
240 | pkt += dhcp6_add_option(DHCP6_OPTION_IA_NA, pkt); | |
241 | pkt += dhcp6_add_option(DHCP6_OPTION_ORO, pkt); | |
242 | /* copy received IA_TA/IA_NA into the REQUEST packet */ | |
243 | if (sm_params.server_uid.uid_ptr) { | |
244 | memcpy(pkt, sm_params.server_uid.uid_ptr, sm_params.server_uid.uid_size); | |
245 | pkt += sm_params.server_uid.uid_size; | |
246 | } | |
247 | if (CONFIG_DHCP6_PXE_CLIENTARCH != 0xFF) | |
248 | pkt += dhcp6_add_option(DHCP6_OPTION_CLIENT_ARCH_TYPE, pkt); | |
249 | pkt += dhcp6_add_option(DHCP6_OPTION_VENDOR_CLASS, pkt); | |
250 | pkt += dhcp6_add_option(DHCP6_OPTION_NII, pkt); | |
251 | ||
252 | /* calculate packet length */ | |
253 | len = pkt - dhcp_pkt_start_ptr; | |
254 | ||
255 | /* send UDP packet to DHCP6 multicast address */ | |
256 | string_to_ip6(DHCP6_MULTICAST_ADDR, strlen(DHCP6_MULTICAST_ADDR), &dhcp_bcast_ip6); | |
257 | net_set_udp_handler(dhcp6_handler); | |
258 | net_send_udp_packet6((uchar *)net_bcast_ethaddr, &dhcp_bcast_ip6, | |
259 | PORT_DHCP6_S, PORT_DHCP6_C, len); | |
260 | } | |
261 | ||
262 | static void dhcp6_parse_ia_options(struct dhcp6_option_hdr *ia_ptr, uchar *ia_option_ptr) | |
263 | { | |
264 | struct dhcp6_option_hdr *ia_option_hdr; | |
265 | ||
266 | ia_option_hdr = (struct dhcp6_option_hdr *)ia_option_ptr; | |
267 | ||
268 | /* Search for options encapsulated in IA_NA/IA_TA (DHCP6_OPTION_IAADDR | |
269 | * or DHCP6_OPTION_STATUS_CODE) | |
270 | */ | |
271 | while (ia_option_ptr < ((uchar *)ia_ptr + ntohs(ia_ptr->option_len))) { | |
272 | switch (ntohs(ia_option_hdr->option_id)) { | |
273 | case DHCP6_OPTION_IAADDR: | |
274 | sm_params.rx_status.ia_addr_found = true; | |
275 | net_copy_ip6(&sm_params.rx_status.ia_addr_ipv6, | |
276 | (ia_option_ptr + sizeof(struct dhcp6_hdr))); | |
277 | debug("DHCP6_OPTION_IAADDR FOUND\n"); | |
278 | break; | |
279 | case DHCP6_OPTION_STATUS_CODE: | |
280 | sm_params.rx_status.ia_status_code = | |
281 | ntohs(*((u16 *)(ia_option_ptr + sizeof(struct dhcp6_hdr)))); | |
282 | printf("ERROR : IA STATUS %d\n", sm_params.rx_status.ia_status_code); | |
283 | break; | |
284 | default: | |
285 | debug("Unknown Option in IA, skipping\n"); | |
286 | break; | |
287 | } | |
288 | ||
289 | ia_option_ptr += ntohs(((struct dhcp6_option_hdr *)ia_option_ptr)->option_len); | |
290 | } | |
291 | } | |
292 | ||
293 | /** | |
294 | * dhcp6_parse_options() - Parse the DHCP6 options | |
295 | * | |
296 | * @rx_pkt: pointer to beginning of received DHCP6 packet | |
297 | * @len: Total length of the DHCP6 packet | |
298 | * | |
299 | * Parses the DHCP options from a received DHCP packet. Perform error checking | |
300 | * on the options received. Any relevant status is available in: | |
301 | * "sm_params.rx_status" | |
302 | * | |
303 | */ | |
304 | static void dhcp6_parse_options(uchar *rx_pkt, unsigned int len) | |
305 | { | |
306 | uchar *option_ptr; | |
307 | int sol_max_rt_sec, option_len; | |
308 | char *s, *e; | |
309 | struct dhcp6_option_hdr *option_hdr; | |
310 | ||
311 | memset(&sm_params.rx_status, 0, sizeof(struct dhcp6_rx_pkt_status)); | |
312 | ||
313 | option_hdr = (struct dhcp6_option_hdr *)(rx_pkt + sizeof(struct dhcp6_hdr)); | |
314 | /* check that required options exist */ | |
315 | while (option_hdr < (struct dhcp6_option_hdr *)(rx_pkt + len)) { | |
316 | option_ptr = ((uchar *)option_hdr) + sizeof(struct dhcp6_hdr); | |
317 | option_len = ntohs(option_hdr->option_len); | |
318 | ||
1b3117db SE |
319 | if (option_ptr + option_len > rx_pkt + len) { |
320 | debug("Invalid option length\n"); | |
321 | return; | |
322 | } | |
323 | ||
a0245818 SE |
324 | switch (ntohs(option_hdr->option_id)) { |
325 | case DHCP6_OPTION_CLIENTID: | |
326 | if (memcmp(option_ptr, sm_params.duid, option_len) | |
327 | != 0) { | |
328 | debug("CLIENT ID DOESN'T MATCH\n"); | |
329 | } else { | |
330 | debug("CLIENT ID FOUND and MATCHES\n"); | |
331 | sm_params.rx_status.client_id_match = true; | |
332 | } | |
333 | break; | |
334 | case DHCP6_OPTION_SERVERID: | |
335 | sm_params.rx_status.server_id_found = true; | |
336 | sm_params.rx_status.server_uid_ptr = (uchar *)option_hdr; | |
337 | sm_params.rx_status.server_uid_size = option_len + | |
338 | sizeof(struct dhcp6_option_hdr); | |
339 | debug("SERVER ID FOUND\n"); | |
340 | break; | |
341 | case DHCP6_OPTION_IA_TA: | |
342 | case DHCP6_OPTION_IA_NA: | |
343 | /* check the IA_ID */ | |
344 | if (*((u32 *)option_ptr) != htonl(sm_params.ia_id)) { | |
345 | debug("IA_ID mismatch 0x%08x 0x%08x\n", | |
346 | *((u32 *)option_ptr), htonl(sm_params.ia_id)); | |
347 | break; | |
348 | } | |
349 | ||
350 | if (ntohs(option_hdr->option_id) == DHCP6_OPTION_IA_NA) { | |
351 | /* skip past IA_ID/T1/T2 */ | |
352 | option_ptr += 3 * sizeof(u32); | |
353 | } else if (ntohs(option_hdr->option_id) == DHCP6_OPTION_IA_TA) { | |
354 | /* skip past IA_ID */ | |
355 | option_ptr += sizeof(u32); | |
356 | } | |
357 | /* parse the IA_NA/IA_TA encapsulated options */ | |
358 | dhcp6_parse_ia_options(option_hdr, option_ptr); | |
359 | break; | |
360 | case DHCP6_OPTION_STATUS_CODE: | |
361 | debug("DHCP6_OPTION_STATUS_CODE FOUND\n"); | |
362 | sm_params.rx_status.status_code = ntohs(*((u16 *)option_ptr)); | |
363 | debug("DHCP6 top-level status code %d\n", sm_params.rx_status.status_code); | |
364 | debug("DHCP6 status message: %.*s\n", len, option_ptr + 2); | |
365 | break; | |
366 | case DHCP6_OPTION_SOL_MAX_RT: | |
367 | debug("DHCP6_OPTION_SOL_MAX_RT FOUND\n"); | |
368 | sol_max_rt_sec = ntohl(*((u32 *)option_ptr)); | |
369 | ||
370 | /* A DHCP client MUST ignore any SOL_MAX_RT option values that are less | |
371 | * than 60 or more than 86400 | |
372 | */ | |
373 | if (sol_max_rt_sec >= 60 && sol_max_rt_sec <= 86400) { | |
374 | updated_sol_max_rt_ms = sol_max_rt_sec * 1000; | |
375 | if (sm_params.curr_state == DHCP6_SOLICIT) | |
376 | sm_params.mrt_ms = updated_sol_max_rt_ms; | |
377 | } | |
378 | break; | |
379 | case DHCP6_OPTION_OPT_BOOTFILE_URL: | |
380 | debug("DHCP6_OPTION_OPT_BOOTFILE_URL FOUND\n"); | |
381 | copy_filename(net_boot_file_name, option_ptr, option_len + 1); | |
382 | debug("net_boot_file_name: %s\n", net_boot_file_name); | |
383 | ||
384 | /* copy server_ip6 (required for PXE) */ | |
385 | s = strchr(net_boot_file_name, '['); | |
386 | e = strchr(net_boot_file_name, ']'); | |
387 | if (s && e && e > s) | |
388 | string_to_ip6(s + 1, e - s - 1, &net_server_ip6); | |
389 | break; | |
390 | case DHCP6_OPTION_OPT_BOOTFILE_PARAM: | |
391 | if (IS_ENABLED(CONFIG_DHCP6_PXE_DHCP_OPTION)) { | |
392 | debug("DHCP6_OPTION_OPT_BOOTFILE_PARAM FOUND\n"); | |
393 | ||
394 | if (pxelinux_configfile) | |
395 | free(pxelinux_configfile); | |
396 | ||
397 | pxelinux_configfile = (char *)malloc((option_len + 1) * | |
398 | sizeof(char)); | |
399 | if (pxelinux_configfile) | |
400 | strlcpy(pxelinux_configfile, option_ptr, option_len + 1); | |
401 | else | |
402 | printf("Error: Failed to allocate pxelinux_configfile\n"); | |
403 | ||
404 | debug("PXE CONFIG FILE %s\n", pxelinux_configfile); | |
405 | } | |
406 | break; | |
407 | case DHCP6_OPTION_PREFERENCE: | |
408 | debug("DHCP6_OPTION_PREFERENCE FOUND\n"); | |
409 | sm_params.rx_status.preference = *option_ptr; | |
410 | break; | |
411 | default: | |
412 | debug("Unknown Option ID: %d, skipping parsing\n", | |
413 | ntohs(option_hdr->option_id)); | |
414 | break; | |
415 | } | |
416 | /* Increment to next option header */ | |
417 | option_hdr = (struct dhcp6_option_hdr *)(((uchar *)option_hdr) + | |
418 | sizeof(struct dhcp6_option_hdr) + option_len); | |
419 | } | |
420 | } | |
421 | ||
422 | /** | |
423 | * dhcp6_check_advertise_packet() - Perform error checking on an expected | |
424 | * ADVERTISE packet. | |
425 | * | |
426 | * @rx_pkt: pointer to beginning of received DHCP6 packet | |
427 | * @len: Total length of the DHCP6 packet | |
428 | * | |
429 | * Implements RFC 8415: | |
430 | * - 16.3. Advertise Message | |
431 | * - 18.2.10. Receipt of Reply Messages | |
432 | * | |
433 | * Return : 0 : ADVERTISE packet was received with no errors. | |
434 | * State machine can progress | |
435 | * 1 : - packet received is not an ADVERTISE packet | |
436 | * - there were errors in the packet received, | |
437 | * - this is the first SOLICIT packet, but | |
438 | * received preference is not 255, so we have | |
439 | * to wait for more server responses. | |
440 | */ | |
441 | static int dhcp6_check_advertise_packet(uchar *rx_pkt, unsigned int len) | |
442 | { | |
443 | u16 rx_uid_size; | |
444 | struct dhcp6_hdr *dhcp6_hdr = (struct dhcp6_hdr *)rx_pkt; | |
445 | ||
446 | /* Ignore message if msg-type != advertise */ | |
447 | if (dhcp6_hdr->msg_type != DHCP6_MSG_ADVERTISE) | |
448 | return 1; | |
449 | /* Ignore message if transaction ID doesn't match */ | |
450 | if (dhcp6_hdr->trans_id != htons(sm_params.trans_id)) | |
451 | return 1; | |
452 | ||
453 | dhcp6_parse_options(rx_pkt, len); | |
454 | ||
455 | /* Ignore advertise if any of these conditions met */ | |
456 | if (!sm_params.rx_status.server_id_found || | |
457 | !sm_params.rx_status.client_id_match || | |
458 | sm_params.rx_status.status_code != DHCP6_SUCCESS) { | |
459 | return 1; | |
460 | } | |
461 | ||
462 | if (sm_params.rx_status.server_id_found) { | |
463 | /* if no server UID has been received yet, or if the server UID | |
464 | * received has a higher preference value than the currently saved | |
465 | * server UID, save the new server UID and preference | |
466 | */ | |
467 | if (!sm_params.server_uid.uid_ptr || | |
468 | (sm_params.server_uid.uid_ptr && | |
469 | sm_params.server_uid.preference < sm_params.rx_status.preference)) { | |
470 | rx_uid_size = sm_params.rx_status.server_uid_size; | |
471 | if (sm_params.server_uid.uid_ptr) | |
472 | free(sm_params.server_uid.uid_ptr); | |
473 | sm_params.server_uid.uid_ptr = malloc(rx_uid_size * sizeof(uchar)); | |
474 | if (sm_params.server_uid.uid_ptr) | |
475 | memcpy(sm_params.server_uid.uid_ptr, | |
476 | sm_params.rx_status.server_uid_ptr, rx_uid_size); | |
477 | ||
478 | sm_params.server_uid.uid_size = rx_uid_size; | |
479 | sm_params.server_uid.preference = sm_params.rx_status.preference; | |
480 | } | |
481 | ||
482 | /* If the first SOLICIT and preference code is 255, use right away. | |
483 | * Otherwise, wait for the first SOLICIT period for more | |
484 | * DHCP6 servers to respond. | |
485 | */ | |
486 | if (sm_params.retry_cnt == 1 && | |
487 | sm_params.server_uid.preference != 255) { | |
488 | debug("valid ADVERTISE, waiting for first SOLICIT period\n"); | |
489 | return 1; | |
490 | } | |
491 | } | |
492 | ||
493 | return 0; | |
494 | } | |
495 | ||
496 | /** | |
497 | * dhcp6_check_reply_packet() - Perform error checking on an expected | |
498 | * REPLY packet. | |
499 | * | |
500 | * @rx_pkt: pointer to beginning of received DHCP6 packet | |
501 | * @len: Total length of the DHCP6 packet | |
502 | * | |
503 | * Implements RFC 8415: | |
504 | * - 16.10. Reply Message | |
505 | * - 18.2.10. Receipt of Reply Messages | |
506 | * | |
507 | * Return : 0 - REPLY packet was received with no errors | |
508 | * 1 - packet received is not an REPLY packet, | |
509 | * or there were errors in the packet received | |
510 | */ | |
511 | static int dhcp6_check_reply_packet(uchar *rx_pkt, unsigned int len) | |
512 | { | |
513 | struct dhcp6_hdr *dhcp6_hdr = (struct dhcp6_hdr *)rx_pkt; | |
514 | ||
515 | /* Ignore message if msg-type != reply */ | |
516 | if (dhcp6_hdr->msg_type != DHCP6_MSG_REPLY) | |
517 | return 1; | |
518 | /* check that transaction ID matches */ | |
519 | if (dhcp6_hdr->trans_id != htons(sm_params.trans_id)) | |
520 | return 1; | |
521 | ||
522 | dhcp6_parse_options(rx_pkt, len); | |
523 | ||
524 | /* if no addresses found, restart DHCP */ | |
525 | if (!sm_params.rx_status.ia_addr_found || | |
526 | sm_params.rx_status.ia_status_code == DHCP6_NO_ADDRS_AVAIL || | |
527 | sm_params.rx_status.status_code == DHCP6_NOT_ON_LINK) { | |
528 | /* restart DHCP */ | |
529 | debug("No address found in reply. Restarting DHCP\n"); | |
530 | dhcp6_start(); | |
531 | } | |
532 | ||
533 | /* ignore reply if any of these conditions met */ | |
534 | if (!sm_params.rx_status.server_id_found || | |
535 | !sm_params.rx_status.client_id_match || | |
536 | sm_params.rx_status.status_code == DHCP6_UNSPEC_FAIL) { | |
537 | return 1; | |
538 | } | |
539 | ||
540 | return 0; | |
541 | } | |
542 | ||
543 | /* Timeout for DHCP6 SOLICIT/REQUEST */ | |
544 | static void dhcp6_timeout_handler(void) | |
545 | { | |
546 | /* call state machine with the timeout flag */ | |
547 | dhcp6_state_machine(true, NULL, 0); | |
548 | } | |
549 | ||
550 | /** | |
551 | * dhcp6_state_machine() - DHCP6 state machine | |
552 | * | |
553 | * @timeout: TRUE : timeout waiting for response from | |
554 | * DHCP6 server | |
555 | * FALSE : init or received response from DHCP6 server | |
556 | * @rx_pkt: Pointer to the beginning of received DHCP6 packet. | |
557 | * Will be NULL if called as part of init | |
558 | * or timeout==TRUE | |
559 | * @len: Total length of the DHCP6 packet if rx_pkt != NULL | |
560 | * | |
561 | * Implements RFC 8415: | |
562 | * - 5.2. Client/Server Exchanges Involving Four Messages | |
563 | * - 15. Reliability of Client-Initiated Message Exchanges | |
564 | * | |
565 | * Handles: | |
566 | * - transmission of SOLICIT and REQUEST packets | |
567 | * - retransmission of SOLICIT and REQUEST packets if no | |
568 | * response is received within the timeout window | |
569 | * - checking received ADVERTISE and REPLY packets to | |
570 | * assess if the DHCP state machine can progress | |
571 | */ | |
572 | static void dhcp6_state_machine(bool timeout, uchar *rx_pkt, unsigned int len) | |
573 | { | |
574 | int rand_minus_plus_100; | |
575 | ||
576 | switch (sm_params.curr_state) { | |
577 | case DHCP6_INIT: | |
578 | sm_params.next_state = DHCP6_SOLICIT; | |
579 | break; | |
580 | case DHCP6_SOLICIT: | |
581 | if (!timeout) { | |
582 | /* check the rx packet and determine if we can transition to next | |
583 | * state. | |
584 | */ | |
585 | if (dhcp6_check_advertise_packet(rx_pkt, len)) | |
586 | return; | |
587 | ||
588 | debug("ADVERTISE good, transition to REQUEST\n"); | |
589 | sm_params.next_state = DHCP6_REQUEST; | |
590 | } else if (sm_params.retry_cnt == 1) { | |
591 | /* If a server UID was received in the first SOLICIT period | |
592 | * transition to REQUEST | |
593 | */ | |
594 | if (sm_params.server_uid.uid_ptr) | |
595 | sm_params.next_state = DHCP6_REQUEST; | |
596 | } | |
597 | break; | |
598 | case DHCP6_REQUEST: | |
599 | if (!timeout) { | |
600 | /* check the rx packet and determine if we can transition to next state */ | |
601 | if (dhcp6_check_reply_packet(rx_pkt, len)) | |
602 | return; | |
603 | ||
604 | debug("REPLY good, transition to DONE\n"); | |
605 | sm_params.next_state = DHCP6_DONE; | |
606 | } | |
607 | break; | |
608 | case DHCP6_DONE: | |
609 | case DHCP6_FAIL: | |
610 | /* Shouldn't get here, as state machine should exit | |
611 | * immediately when DHCP6_DONE or DHCP6_FAIL is entered. | |
612 | * Proceed anyway to proceed DONE/FAIL actions | |
613 | */ | |
614 | debug("Unexpected DHCP6 state : %d\n", sm_params.curr_state); | |
615 | break; | |
616 | } | |
617 | /* re-seed the RNG */ | |
618 | srand(get_ticks() + rand()); | |
619 | ||
620 | /* handle state machine entry conditions */ | |
621 | if (sm_params.curr_state != sm_params.next_state) { | |
622 | sm_params.retry_cnt = 0; | |
623 | ||
624 | if (sm_params.next_state == DHCP6_SOLICIT) { | |
625 | /* delay a random ammount (special for SOLICIT) */ | |
626 | udelay((rand() % SOL_MAX_DELAY_MS) * 1000); | |
627 | /* init timestamp variables after SOLICIT delay */ | |
628 | sm_params.dhcp6_start_ms = get_timer(0); | |
629 | sm_params.dhcp6_retry_start_ms = sm_params.dhcp6_start_ms; | |
630 | sm_params.dhcp6_retry_ms = sm_params.dhcp6_start_ms; | |
631 | /* init transaction and ia_id */ | |
632 | sm_params.trans_id = rand() & 0xFFFFFF; | |
633 | sm_params.ia_id = rand(); | |
634 | /* initialize retransmission parameters */ | |
635 | sm_params.irt_ms = SOL_TIMEOUT_MS; | |
636 | sm_params.mrt_ms = updated_sol_max_rt_ms; | |
637 | /* RFCs default MRC is be 0 (try infinitely) | |
638 | * give up after CONFIG_NET_RETRY_COUNT number of tries (same as DHCPv4) | |
639 | */ | |
640 | sm_params.mrc = CONFIG_NET_RETRY_COUNT; | |
641 | sm_params.mrd_ms = 0; | |
642 | ||
643 | } else if (sm_params.next_state == DHCP6_REQUEST) { | |
644 | /* init timestamp variables */ | |
645 | sm_params.dhcp6_retry_start_ms = get_timer(0); | |
646 | sm_params.dhcp6_retry_ms = sm_params.dhcp6_start_ms; | |
647 | /* initialize retransmission parameters */ | |
648 | sm_params.irt_ms = REQ_TIMEOUT_MS; | |
649 | sm_params.mrt_ms = REQ_MAX_RT_MS; | |
650 | sm_params.mrc = REQ_MAX_RC; | |
651 | sm_params.mrd_ms = 0; | |
652 | } | |
653 | } | |
654 | ||
655 | if (timeout) | |
656 | sm_params.dhcp6_retry_ms = get_timer(0); | |
657 | ||
658 | /* Check if MRC or MRD have been passed */ | |
659 | if ((sm_params.mrc != 0 && | |
660 | sm_params.retry_cnt >= sm_params.mrc) || | |
661 | (sm_params.mrd_ms != 0 && | |
662 | ((sm_params.dhcp6_retry_ms - sm_params.dhcp6_retry_start_ms) >= sm_params.mrd_ms))) { | |
663 | sm_params.next_state = DHCP6_FAIL; | |
664 | } | |
665 | ||
666 | /* calculate retransmission timeout (RT) */ | |
667 | rand_minus_plus_100 = ((rand() % 200) - 100); | |
668 | if (sm_params.retry_cnt == 0) { | |
669 | sm_params.rt_ms = sm_params.irt_ms + | |
670 | ((sm_params.irt_ms * rand_minus_plus_100) / 1000); | |
671 | } else { | |
672 | sm_params.rt_ms = (2 * sm_params.rt_prev_ms) + | |
673 | ((sm_params.rt_prev_ms * rand_minus_plus_100) / 1000); | |
674 | } | |
675 | ||
676 | if (sm_params.rt_ms > sm_params.mrt_ms) { | |
677 | sm_params.rt_ms = sm_params.mrt_ms + | |
678 | ((sm_params.mrt_ms * rand_minus_plus_100) / 1000); | |
679 | } | |
680 | ||
681 | sm_params.rt_prev_ms = sm_params.rt_ms; | |
682 | ||
683 | net_set_timeout_handler(sm_params.rt_ms, dhcp6_timeout_handler); | |
684 | ||
685 | /* send transmit/retransmit message or fail */ | |
686 | sm_params.curr_state = sm_params.next_state; | |
687 | ||
688 | if (sm_params.curr_state == DHCP6_SOLICIT) { | |
689 | /* send solicit packet */ | |
690 | dhcp6_send_solicit_packet(); | |
691 | printf("DHCP6 SOLICIT %d\n", sm_params.retry_cnt); | |
692 | } else if (sm_params.curr_state == DHCP6_REQUEST) { | |
693 | /* send request packet */ | |
694 | dhcp6_send_request_packet(); | |
695 | printf("DHCP6 REQUEST %d\n", sm_params.retry_cnt); | |
696 | } else if (sm_params.curr_state == DHCP6_DONE) { | |
697 | net_set_timeout_handler(0, NULL); | |
698 | ||
699 | /* Duplicate address detection (DAD) should be | |
700 | * performed here before setting net_ip6 | |
701 | * (enhancement should be considered) | |
702 | */ | |
703 | net_copy_ip6(&net_ip6, &sm_params.rx_status.ia_addr_ipv6); | |
704 | printf("DHCP6 client bound to %pI6c\n", &net_ip6); | |
705 | /* will load with TFTP6 */ | |
706 | net_auto_load(); | |
707 | } else if (sm_params.curr_state == DHCP6_FAIL) { | |
708 | printf("DHCP6 FAILED, TERMINATING\n"); | |
709 | net_set_state(NETLOOP_FAIL); | |
710 | } | |
711 | sm_params.retry_cnt++; | |
712 | } | |
713 | ||
714 | /* Start or restart DHCP6 */ | |
715 | void dhcp6_start(void) | |
716 | { | |
717 | memset(&sm_params, 0, sizeof(struct dhcp6_sm_params)); | |
718 | ||
719 | /* seed the RNG with MAC address */ | |
720 | srand_mac(); | |
721 | ||
722 | sm_params.curr_state = DHCP6_INIT; | |
723 | dhcp6_state_machine(false, NULL, 0); | |
724 | } |