Line data Source code
1 : /* SPDX-License-Identifier: LGPL-2.1+ */
2 : /***
3 : Copyright © 2014-2015 Intel Corporation. All rights reserved.
4 : ***/
5 :
6 : #include <errno.h>
7 : #include <string.h>
8 : #include <sys/ioctl.h>
9 : #include <linux/if_arp.h>
10 : #include <linux/if_infiniband.h>
11 :
12 : #include "sd-dhcp6-client.h"
13 :
14 : #include "alloc-util.h"
15 : #include "dhcp-identifier.h"
16 : #include "dhcp6-internal.h"
17 : #include "dhcp6-lease-internal.h"
18 : #include "dhcp6-protocol.h"
19 : #include "dns-domain.h"
20 : #include "event-util.h"
21 : #include "fd-util.h"
22 : #include "hostname-util.h"
23 : #include "in-addr-util.h"
24 : #include "network-internal.h"
25 : #include "random-util.h"
26 : #include "socket-util.h"
27 : #include "string-table.h"
28 : #include "util.h"
29 :
30 : #define MAX_MAC_ADDR_LEN INFINIBAND_ALEN
31 :
32 : /* what to request from the server, addresses (IA_NA) and/or prefixes (IA_PD) */
33 : enum {
34 : DHCP6_REQUEST_IA_NA = 1,
35 : DHCP6_REQUEST_IA_TA = 2, /* currently not used */
36 : DHCP6_REQUEST_IA_PD = 4,
37 : };
38 :
39 : struct sd_dhcp6_client {
40 : unsigned n_ref;
41 :
42 : enum DHCP6State state;
43 : sd_event *event;
44 : int event_priority;
45 : int ifindex;
46 : struct in6_addr local_address;
47 : uint8_t mac_addr[MAX_MAC_ADDR_LEN];
48 : size_t mac_addr_len;
49 : uint16_t arp_type;
50 : DHCP6IA ia_na;
51 : DHCP6IA ia_pd;
52 : sd_event_source *timeout_t1;
53 : sd_event_source *timeout_t2;
54 : unsigned request;
55 : be32_t transaction_id;
56 : usec_t transaction_start;
57 : struct sd_dhcp6_lease *lease;
58 : int fd;
59 : bool information_request;
60 : bool iaid_set;
61 : be16_t *req_opts;
62 : size_t req_opts_allocated;
63 : size_t req_opts_len;
64 : char *fqdn;
65 : sd_event_source *receive_message;
66 : usec_t retransmit_time;
67 : uint8_t retransmit_count;
68 : sd_event_source *timeout_resend;
69 : sd_event_source *timeout_resend_expire;
70 : sd_dhcp6_client_callback_t callback;
71 : void *userdata;
72 : struct duid duid;
73 : size_t duid_len;
74 : };
75 :
76 : static const uint16_t default_req_opts[] = {
77 : SD_DHCP6_OPTION_DNS_SERVERS,
78 : SD_DHCP6_OPTION_DOMAIN_LIST,
79 : SD_DHCP6_OPTION_NTP_SERVER,
80 : SD_DHCP6_OPTION_SNTP_SERVERS,
81 : };
82 :
83 : const char * dhcp6_message_type_table[_DHCP6_MESSAGE_MAX] = {
84 : [DHCP6_SOLICIT] = "SOLICIT",
85 : [DHCP6_ADVERTISE] = "ADVERTISE",
86 : [DHCP6_REQUEST] = "REQUEST",
87 : [DHCP6_CONFIRM] = "CONFIRM",
88 : [DHCP6_RENEW] = "RENEW",
89 : [DHCP6_REBIND] = "REBIND",
90 : [DHCP6_REPLY] = "REPLY",
91 : [DHCP6_RELEASE] = "RELEASE",
92 : [DHCP6_DECLINE] = "DECLINE",
93 : [DHCP6_RECONFIGURE] = "RECONFIGURE",
94 : [DHCP6_INFORMATION_REQUEST] = "INFORMATION-REQUEST",
95 : [DHCP6_RELAY_FORW] = "RELAY-FORW",
96 : [DHCP6_RELAY_REPL] = "RELAY-REPL",
97 : };
98 :
99 38 : DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int);
100 :
101 : const char * dhcp6_message_status_table[_DHCP6_STATUS_MAX] = {
102 : [DHCP6_STATUS_SUCCESS] = "Success",
103 : [DHCP6_STATUS_UNSPEC_FAIL] = "Unspecified failure",
104 : [DHCP6_STATUS_NO_ADDRS_AVAIL] = "No addresses available",
105 : [DHCP6_STATUS_NO_BINDING] = "Binding unavailable",
106 : [DHCP6_STATUS_NOT_ON_LINK] = "Not on link",
107 : [DHCP6_STATUS_USE_MULTICAST] = "Use multicast",
108 : };
109 :
110 16 : DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int);
111 :
112 : #define DHCP6_CLIENT_DONT_DESTROY(client) \
113 : _cleanup_(sd_dhcp6_client_unrefp) _unused_ sd_dhcp6_client *_dont_destroy_##client = sd_dhcp6_client_ref(client)
114 :
115 : static int client_start(sd_dhcp6_client *client, enum DHCP6State state);
116 :
117 4 : int sd_dhcp6_client_set_callback(
118 : sd_dhcp6_client *client,
119 : sd_dhcp6_client_callback_t cb,
120 : void *userdata) {
121 :
122 4 : assert_return(client, -EINVAL);
123 :
124 4 : client->callback = cb;
125 4 : client->userdata = userdata;
126 :
127 4 : return 0;
128 : }
129 :
130 5 : int sd_dhcp6_client_set_ifindex(sd_dhcp6_client *client, int ifindex) {
131 :
132 5 : assert_return(client, -EINVAL);
133 5 : assert_return(ifindex >= -1, -EINVAL);
134 4 : assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
135 :
136 4 : client->ifindex = ifindex;
137 4 : return 0;
138 : }
139 :
140 2 : int sd_dhcp6_client_set_local_address(
141 : sd_dhcp6_client *client,
142 : const struct in6_addr *local_address) {
143 :
144 2 : assert_return(client, -EINVAL);
145 2 : assert_return(local_address, -EINVAL);
146 2 : assert_return(in_addr_is_link_local(AF_INET6, (const union in_addr_union *) local_address) > 0, -EINVAL);
147 :
148 2 : assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
149 :
150 2 : client->local_address = *local_address;
151 :
152 2 : return 0;
153 : }
154 :
155 2 : int sd_dhcp6_client_set_mac(
156 : sd_dhcp6_client *client,
157 : const uint8_t *addr, size_t addr_len,
158 : uint16_t arp_type) {
159 :
160 2 : assert_return(client, -EINVAL);
161 2 : assert_return(addr, -EINVAL);
162 2 : assert_return(addr_len > 0 && addr_len <= MAX_MAC_ADDR_LEN, -EINVAL);
163 2 : assert_return(arp_type > 0, -EINVAL);
164 :
165 2 : assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
166 :
167 2 : if (arp_type == ARPHRD_ETHER)
168 2 : assert_return(addr_len == ETH_ALEN, -EINVAL);
169 0 : else if (arp_type == ARPHRD_INFINIBAND)
170 0 : assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
171 : else
172 0 : return -EINVAL;
173 :
174 2 : if (client->mac_addr_len == addr_len &&
175 0 : memcmp(&client->mac_addr, addr, addr_len) == 0)
176 0 : return 0;
177 :
178 2 : memcpy(&client->mac_addr, addr, addr_len);
179 2 : client->mac_addr_len = addr_len;
180 2 : client->arp_type = arp_type;
181 :
182 2 : return 0;
183 : }
184 :
185 2 : static int client_ensure_duid(sd_dhcp6_client *client) {
186 2 : if (client->duid_len != 0)
187 1 : return 0;
188 :
189 1 : return dhcp_identifier_set_duid_en(&client->duid, &client->duid_len);
190 : }
191 :
192 : /**
193 : * Sets DUID. If duid is non-null, the DUID is set to duid_type + duid
194 : * without further modification. Otherwise, if duid_type is supported, DUID
195 : * is set based on that type. Otherwise, an error is returned.
196 : */
197 0 : static int dhcp6_client_set_duid_internal(
198 : sd_dhcp6_client *client,
199 : uint16_t duid_type,
200 : const void *duid,
201 : size_t duid_len,
202 : usec_t llt_time) {
203 : int r;
204 :
205 0 : assert_return(client, -EINVAL);
206 0 : assert_return(duid_len == 0 || duid != NULL, -EINVAL);
207 0 : assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
208 :
209 0 : if (duid != NULL) {
210 0 : r = dhcp_validate_duid_len(duid_type, duid_len, true);
211 0 : if (r < 0) {
212 0 : r = dhcp_validate_duid_len(duid_type, duid_len, false);
213 0 : if (r < 0)
214 0 : return r;
215 0 : log_dhcp6_client(client, "Setting DUID of type %u with unexpected content", duid_type);
216 : }
217 :
218 0 : client->duid.type = htobe16(duid_type);
219 0 : memcpy(&client->duid.raw.data, duid, duid_len);
220 0 : client->duid_len = sizeof(client->duid.type) + duid_len;
221 : } else
222 0 : switch (duid_type) {
223 0 : case DUID_TYPE_LLT:
224 0 : if (client->mac_addr_len == 0)
225 0 : return -EOPNOTSUPP;
226 :
227 0 : r = dhcp_identifier_set_duid_llt(&client->duid, llt_time, client->mac_addr, client->mac_addr_len, client->arp_type, &client->duid_len);
228 0 : if (r < 0)
229 0 : return r;
230 0 : break;
231 0 : case DUID_TYPE_EN:
232 0 : r = dhcp_identifier_set_duid_en(&client->duid, &client->duid_len);
233 0 : if (r < 0)
234 0 : return r;
235 0 : break;
236 0 : case DUID_TYPE_LL:
237 0 : if (client->mac_addr_len == 0)
238 0 : return -EOPNOTSUPP;
239 :
240 0 : r = dhcp_identifier_set_duid_ll(&client->duid, client->mac_addr, client->mac_addr_len, client->arp_type, &client->duid_len);
241 0 : if (r < 0)
242 0 : return r;
243 0 : break;
244 0 : case DUID_TYPE_UUID:
245 0 : r = dhcp_identifier_set_duid_uuid(&client->duid, &client->duid_len);
246 0 : if (r < 0)
247 0 : return r;
248 0 : break;
249 0 : default:
250 0 : return -EINVAL;
251 : }
252 :
253 0 : return 0;
254 : }
255 :
256 0 : int sd_dhcp6_client_set_duid(
257 : sd_dhcp6_client *client,
258 : uint16_t duid_type,
259 : const void *duid,
260 : size_t duid_len) {
261 0 : return dhcp6_client_set_duid_internal(client, duid_type, duid, duid_len, 0);
262 : }
263 :
264 0 : int sd_dhcp6_client_set_duid_llt(
265 : sd_dhcp6_client *client,
266 : usec_t llt_time) {
267 0 : return dhcp6_client_set_duid_internal(client, DUID_TYPE_LLT, NULL, 0, llt_time);
268 : }
269 :
270 0 : int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) {
271 0 : assert_return(client, -EINVAL);
272 0 : assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
273 :
274 0 : client->ia_na.ia_na.id = htobe32(iaid);
275 0 : client->ia_pd.ia_pd.id = htobe32(iaid);
276 0 : client->iaid_set = true;
277 :
278 0 : return 0;
279 : }
280 :
281 6 : int sd_dhcp6_client_set_fqdn(
282 : sd_dhcp6_client *client,
283 : const char *fqdn) {
284 :
285 6 : assert_return(client, -EINVAL);
286 :
287 : /* Make sure FQDN qualifies as DNS and as Linux hostname */
288 6 : if (fqdn &&
289 5 : !(hostname_is_valid(fqdn, false) && dns_name_is_valid(fqdn) > 0))
290 2 : return -EINVAL;
291 :
292 4 : return free_and_strdup(&client->fqdn, fqdn);
293 : }
294 :
295 5 : int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, int enabled) {
296 5 : assert_return(client, -EINVAL);
297 5 : assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
298 :
299 4 : client->information_request = enabled;
300 :
301 4 : return 0;
302 : }
303 :
304 4 : int sd_dhcp6_client_get_information_request(sd_dhcp6_client *client, int *enabled) {
305 4 : assert_return(client, -EINVAL);
306 4 : assert_return(enabled, -EINVAL);
307 :
308 4 : *enabled = client->information_request;
309 :
310 4 : return 0;
311 : }
312 :
313 7 : int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, uint16_t option) {
314 : size_t t;
315 :
316 7 : assert_return(client, -EINVAL);
317 7 : assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
318 :
319 6 : switch(option) {
320 :
321 4 : case SD_DHCP6_OPTION_DNS_SERVERS:
322 : case SD_DHCP6_OPTION_DOMAIN_LIST:
323 : case SD_DHCP6_OPTION_SNTP_SERVERS:
324 : case SD_DHCP6_OPTION_NTP_SERVER:
325 : case SD_DHCP6_OPTION_RAPID_COMMIT:
326 4 : break;
327 :
328 2 : default:
329 2 : return -EINVAL;
330 : }
331 :
332 10 : for (t = 0; t < client->req_opts_len; t++)
333 10 : if (client->req_opts[t] == htobe16(option))
334 4 : return -EEXIST;
335 :
336 0 : if (!GREEDY_REALLOC(client->req_opts, client->req_opts_allocated,
337 : client->req_opts_len + 1))
338 0 : return -ENOMEM;
339 :
340 0 : client->req_opts[client->req_opts_len++] = htobe16(option);
341 :
342 0 : return 0;
343 : }
344 :
345 1 : int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client *client, int *delegation) {
346 1 : assert_return(client, -EINVAL);
347 1 : assert_return(delegation, -EINVAL);
348 :
349 1 : *delegation = FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD);
350 :
351 1 : return 0;
352 : }
353 :
354 1 : int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client, int delegation) {
355 1 : assert_return(client, -EINVAL);
356 :
357 1 : SET_FLAG(client->request, DHCP6_REQUEST_IA_PD, delegation);
358 :
359 1 : return 0;
360 : }
361 :
362 4 : int sd_dhcp6_client_get_address_request(sd_dhcp6_client *client, int *request) {
363 4 : assert_return(client, -EINVAL);
364 4 : assert_return(request, -EINVAL);
365 :
366 4 : *request = FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA);
367 :
368 4 : return 0;
369 : }
370 :
371 3 : int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client, int request) {
372 3 : assert_return(client, -EINVAL);
373 :
374 3 : SET_FLAG(client->request, DHCP6_REQUEST_IA_NA, request);
375 :
376 3 : return 0;
377 : }
378 :
379 0 : int sd_dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id) {
380 0 : assert_return(client, -EINVAL);
381 :
382 0 : client->transaction_id = transaction_id;
383 :
384 0 : return 0;
385 : }
386 :
387 2 : int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
388 2 : assert_return(client, -EINVAL);
389 :
390 2 : if (!client->lease)
391 0 : return -ENOMSG;
392 :
393 2 : if (ret)
394 2 : *ret = client->lease;
395 :
396 2 : return 0;
397 : }
398 :
399 3 : static void client_notify(sd_dhcp6_client *client, int event) {
400 3 : assert(client);
401 :
402 3 : if (client->callback)
403 2 : client->callback(client, event, client->userdata);
404 3 : }
405 :
406 5 : static int client_reset(sd_dhcp6_client *client) {
407 5 : assert(client);
408 :
409 5 : client->lease = sd_dhcp6_lease_unref(client->lease);
410 :
411 5 : client->receive_message =
412 5 : sd_event_source_unref(client->receive_message);
413 :
414 5 : client->transaction_id = 0;
415 5 : client->transaction_start = 0;
416 :
417 5 : client->retransmit_time = 0;
418 5 : client->retransmit_count = 0;
419 :
420 5 : (void) event_source_disable(client->timeout_resend);
421 5 : (void) event_source_disable(client->timeout_resend_expire);
422 5 : (void) event_source_disable(client->timeout_t1);
423 5 : (void) event_source_disable(client->timeout_t2);
424 :
425 5 : client->state = DHCP6_STATE_STOPPED;
426 :
427 5 : return 0;
428 : }
429 :
430 1 : static void client_stop(sd_dhcp6_client *client, int error) {
431 2 : DHCP6_CLIENT_DONT_DESTROY(client);
432 :
433 1 : assert(client);
434 :
435 1 : client_notify(client, error);
436 :
437 1 : client_reset(client);
438 1 : }
439 :
440 3 : static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
441 3 : _cleanup_free_ DHCP6Message *message = NULL;
442 3 : struct in6_addr all_servers =
443 : IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
444 3 : size_t len, optlen = 512;
445 : uint8_t *opt;
446 : int r;
447 : usec_t elapsed_usec;
448 : be16_t elapsed_time;
449 :
450 3 : assert(client);
451 :
452 3 : len = sizeof(DHCP6Message) + optlen;
453 :
454 3 : message = malloc0(len);
455 3 : if (!message)
456 0 : return -ENOMEM;
457 :
458 3 : opt = (uint8_t *)(message + 1);
459 :
460 3 : message->transaction_id = client->transaction_id;
461 :
462 3 : switch(client->state) {
463 1 : case DHCP6_STATE_INFORMATION_REQUEST:
464 1 : message->type = DHCP6_INFORMATION_REQUEST;
465 :
466 1 : break;
467 :
468 1 : case DHCP6_STATE_SOLICITATION:
469 1 : message->type = DHCP6_SOLICIT;
470 :
471 1 : r = dhcp6_option_append(&opt, &optlen,
472 : SD_DHCP6_OPTION_RAPID_COMMIT, 0, NULL);
473 1 : if (r < 0)
474 0 : return r;
475 :
476 1 : if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA)) {
477 1 : r = dhcp6_option_append_ia(&opt, &optlen,
478 1 : &client->ia_na);
479 1 : if (r < 0)
480 0 : return r;
481 : }
482 :
483 1 : if (client->fqdn) {
484 1 : r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn);
485 1 : if (r < 0)
486 0 : return r;
487 : }
488 :
489 1 : if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) {
490 0 : r = dhcp6_option_append_pd(opt, optlen, &client->ia_pd);
491 0 : if (r < 0)
492 0 : return r;
493 :
494 0 : opt += r;
495 0 : optlen -= r;
496 : }
497 :
498 1 : break;
499 :
500 1 : case DHCP6_STATE_REQUEST:
501 : case DHCP6_STATE_RENEW:
502 :
503 1 : if (client->state == DHCP6_STATE_REQUEST)
504 1 : message->type = DHCP6_REQUEST;
505 : else
506 0 : message->type = DHCP6_RENEW;
507 :
508 1 : r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_SERVERID,
509 1 : client->lease->serverid_len,
510 1 : client->lease->serverid);
511 1 : if (r < 0)
512 0 : return r;
513 :
514 1 : if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA)) {
515 1 : r = dhcp6_option_append_ia(&opt, &optlen,
516 1 : &client->lease->ia);
517 1 : if (r < 0)
518 0 : return r;
519 : }
520 :
521 1 : if (client->fqdn) {
522 1 : r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn);
523 1 : if (r < 0)
524 0 : return r;
525 : }
526 :
527 1 : if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) {
528 0 : r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd);
529 0 : if (r < 0)
530 0 : return r;
531 :
532 0 : opt += r;
533 0 : optlen -= r;
534 : }
535 :
536 1 : break;
537 :
538 0 : case DHCP6_STATE_REBIND:
539 0 : message->type = DHCP6_REBIND;
540 :
541 0 : if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA)) {
542 0 : r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia);
543 0 : if (r < 0)
544 0 : return r;
545 : }
546 :
547 0 : if (client->fqdn) {
548 0 : r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn);
549 0 : if (r < 0)
550 0 : return r;
551 : }
552 :
553 0 : if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) {
554 0 : r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd);
555 0 : if (r < 0)
556 0 : return r;
557 :
558 0 : opt += r;
559 0 : optlen -= r;
560 : }
561 :
562 0 : break;
563 :
564 0 : case DHCP6_STATE_STOPPED:
565 : case DHCP6_STATE_BOUND:
566 0 : return -EINVAL;
567 : }
568 :
569 6 : r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_ORO,
570 3 : client->req_opts_len * sizeof(be16_t),
571 3 : client->req_opts);
572 3 : if (r < 0)
573 0 : return r;
574 :
575 3 : assert(client->duid_len);
576 3 : r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_CLIENTID,
577 3 : client->duid_len, &client->duid);
578 3 : if (r < 0)
579 0 : return r;
580 :
581 3 : elapsed_usec = time_now - client->transaction_start;
582 3 : if (elapsed_usec < 0xffff * USEC_PER_MSEC * 10)
583 3 : elapsed_time = htobe16(elapsed_usec / USEC_PER_MSEC / 10);
584 : else
585 0 : elapsed_time = 0xffff;
586 :
587 3 : r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_ELAPSED_TIME,
588 : sizeof(elapsed_time), &elapsed_time);
589 3 : if (r < 0)
590 0 : return r;
591 :
592 3 : r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message,
593 : len - optlen);
594 3 : if (r < 0)
595 0 : return r;
596 :
597 3 : log_dhcp6_client(client, "Sent %s",
598 : dhcp6_message_type_to_string(message->type));
599 :
600 3 : return 0;
601 : }
602 :
603 0 : static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) {
604 0 : sd_dhcp6_client *client = userdata;
605 :
606 0 : assert(s);
607 0 : assert(client);
608 0 : assert(client->lease);
609 :
610 0 : (void) event_source_disable(client->timeout_t2);
611 :
612 0 : log_dhcp6_client(client, "Timeout T2");
613 :
614 0 : client_start(client, DHCP6_STATE_REBIND);
615 :
616 0 : return 0;
617 : }
618 :
619 0 : static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) {
620 0 : sd_dhcp6_client *client = userdata;
621 :
622 0 : assert(s);
623 0 : assert(client);
624 0 : assert(client->lease);
625 :
626 0 : (void) event_source_disable(client->timeout_t1);
627 :
628 0 : log_dhcp6_client(client, "Timeout T1");
629 :
630 0 : client_start(client, DHCP6_STATE_RENEW);
631 :
632 0 : return 0;
633 : }
634 :
635 0 : static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec, void *userdata) {
636 0 : sd_dhcp6_client *client = userdata;
637 0 : DHCP6_CLIENT_DONT_DESTROY(client);
638 : enum DHCP6State state;
639 :
640 0 : assert(s);
641 0 : assert(client);
642 0 : assert(client->event);
643 :
644 0 : state = client->state;
645 :
646 0 : client_stop(client, SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE);
647 :
648 : /* RFC 3315, section 18.1.4., says that "...the client may choose to
649 : use a Solicit message to locate a new DHCP server..." */
650 0 : if (state == DHCP6_STATE_REBIND)
651 0 : client_start(client, DHCP6_STATE_SOLICITATION);
652 :
653 0 : return 0;
654 : }
655 :
656 5 : static usec_t client_timeout_compute_random(usec_t val) {
657 15 : return val - val / 10 +
658 5 : (random_u32() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
659 : }
660 :
661 4 : static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userdata) {
662 4 : int r = 0;
663 4 : sd_dhcp6_client *client = userdata;
664 4 : usec_t time_now, init_retransmit_time = 0, max_retransmit_time = 0;
665 4 : usec_t max_retransmit_duration = 0;
666 4 : uint8_t max_retransmit_count = 0;
667 : char time_string[FORMAT_TIMESPAN_MAX];
668 4 : uint32_t expire = 0;
669 :
670 4 : assert(s);
671 4 : assert(client);
672 4 : assert(client->event);
673 :
674 4 : (void) event_source_disable(client->timeout_resend);
675 :
676 4 : switch (client->state) {
677 1 : case DHCP6_STATE_INFORMATION_REQUEST:
678 1 : init_retransmit_time = DHCP6_INF_TIMEOUT;
679 1 : max_retransmit_time = DHCP6_INF_MAX_RT;
680 :
681 1 : break;
682 :
683 2 : case DHCP6_STATE_SOLICITATION:
684 :
685 2 : if (client->retransmit_count && client->lease) {
686 1 : client_start(client, DHCP6_STATE_REQUEST);
687 1 : return 0;
688 : }
689 :
690 1 : init_retransmit_time = DHCP6_SOL_TIMEOUT;
691 1 : max_retransmit_time = DHCP6_SOL_MAX_RT;
692 :
693 1 : break;
694 :
695 1 : case DHCP6_STATE_REQUEST:
696 1 : init_retransmit_time = DHCP6_REQ_TIMEOUT;
697 1 : max_retransmit_time = DHCP6_REQ_MAX_RT;
698 1 : max_retransmit_count = DHCP6_REQ_MAX_RC;
699 :
700 1 : break;
701 :
702 0 : case DHCP6_STATE_RENEW:
703 0 : init_retransmit_time = DHCP6_REN_TIMEOUT;
704 0 : max_retransmit_time = DHCP6_REN_MAX_RT;
705 :
706 : /* RFC 3315, section 18.1.3. says max retransmit duration will
707 : be the remaining time until T2. Instead of setting MRD,
708 : wait for T2 to trigger with the same end result */
709 :
710 0 : break;
711 :
712 0 : case DHCP6_STATE_REBIND:
713 0 : init_retransmit_time = DHCP6_REB_TIMEOUT;
714 0 : max_retransmit_time = DHCP6_REB_MAX_RT;
715 :
716 0 : if (event_source_is_enabled(client->timeout_resend_expire) <= 0) {
717 0 : r = dhcp6_lease_ia_rebind_expire(&client->lease->ia,
718 : &expire);
719 0 : if (r < 0) {
720 0 : client_stop(client, r);
721 0 : return 0;
722 : }
723 0 : max_retransmit_duration = expire * USEC_PER_SEC;
724 : }
725 :
726 0 : break;
727 :
728 0 : case DHCP6_STATE_STOPPED:
729 : case DHCP6_STATE_BOUND:
730 0 : return 0;
731 : }
732 :
733 3 : if (max_retransmit_count &&
734 1 : client->retransmit_count >= max_retransmit_count) {
735 0 : client_stop(client, SD_DHCP6_CLIENT_EVENT_RETRANS_MAX);
736 0 : return 0;
737 : }
738 :
739 3 : r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
740 3 : if (r < 0)
741 0 : goto error;
742 :
743 3 : r = client_send_message(client, time_now);
744 3 : if (r >= 0)
745 3 : client->retransmit_count++;
746 :
747 3 : if (!client->retransmit_time) {
748 3 : client->retransmit_time =
749 3 : client_timeout_compute_random(init_retransmit_time);
750 :
751 3 : if (client->state == DHCP6_STATE_SOLICITATION)
752 1 : client->retransmit_time += init_retransmit_time / 10;
753 :
754 : } else {
755 0 : if (max_retransmit_time &&
756 0 : client->retransmit_time > max_retransmit_time / 2)
757 0 : client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
758 : else
759 0 : client->retransmit_time += client_timeout_compute_random(client->retransmit_time);
760 : }
761 :
762 3 : log_dhcp6_client(client, "Next retransmission in %s",
763 : format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->retransmit_time, USEC_PER_SEC));
764 :
765 3 : r = event_reset_time(client->event, &client->timeout_resend,
766 : clock_boottime_or_monotonic(),
767 3 : time_now + client->retransmit_time, 10 * USEC_PER_MSEC,
768 : client_timeout_resend, client,
769 3 : client->event_priority, "dhcp6-resend-timer", true);
770 3 : if (r < 0)
771 0 : goto error;
772 :
773 3 : if (max_retransmit_duration && event_source_is_enabled(client->timeout_resend_expire) <= 0) {
774 :
775 0 : log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs",
776 : max_retransmit_duration / USEC_PER_SEC);
777 :
778 0 : r = event_reset_time(client->event, &client->timeout_resend_expire,
779 : clock_boottime_or_monotonic(),
780 : time_now + max_retransmit_duration, USEC_PER_SEC,
781 : client_timeout_resend_expire, client,
782 0 : client->event_priority, "dhcp6-resend-expire-timer", true);
783 0 : if (r < 0)
784 0 : goto error;
785 : }
786 :
787 3 : error:
788 3 : if (r < 0)
789 0 : client_stop(client, r);
790 :
791 3 : return 0;
792 : }
793 :
794 2 : static int client_ensure_iaid(sd_dhcp6_client *client) {
795 : int r;
796 : uint32_t iaid;
797 :
798 2 : assert(client);
799 :
800 2 : if (client->iaid_set)
801 1 : return 0;
802 :
803 1 : r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len, true, &iaid);
804 1 : if (r < 0)
805 0 : return r;
806 :
807 1 : client->ia_na.ia_na.id = iaid;
808 1 : client->ia_pd.ia_pd.id = iaid;
809 1 : client->iaid_set = true;
810 :
811 1 : return 0;
812 : }
813 :
814 3 : static int client_parse_message(
815 : sd_dhcp6_client *client,
816 : DHCP6Message *message,
817 : size_t len,
818 : sd_dhcp6_lease *lease) {
819 :
820 3 : uint32_t lt_t1 = ~0, lt_t2 = ~0;
821 3 : bool clientid = false;
822 3 : size_t pos = 0;
823 : int r;
824 :
825 3 : assert(client);
826 3 : assert(message);
827 3 : assert(len >= sizeof(DHCP6Message));
828 3 : assert(lease);
829 :
830 3 : len -= sizeof(DHCP6Message);
831 :
832 22 : while (pos < len) {
833 19 : DHCP6Option *option = (DHCP6Option *) &message->options[pos];
834 : uint16_t optcode, optlen;
835 : be32_t iaid_lease;
836 : uint8_t *optval;
837 : int status;
838 :
839 19 : if (len < pos + offsetof(DHCP6Option, data))
840 0 : return -ENOBUFS;
841 :
842 19 : optcode = be16toh(option->code);
843 19 : optlen = be16toh(option->len);
844 19 : optval = option->data;
845 :
846 19 : if (len < pos + offsetof(DHCP6Option, data) + optlen)
847 0 : return -ENOBUFS;
848 :
849 19 : switch (optcode) {
850 3 : case SD_DHCP6_OPTION_CLIENTID:
851 3 : if (clientid) {
852 0 : log_dhcp6_client(client, "%s contains multiple clientids",
853 : dhcp6_message_type_to_string(message->type));
854 0 : return -EINVAL;
855 : }
856 :
857 3 : if (optlen != client->duid_len ||
858 3 : memcmp(&client->duid, optval, optlen) != 0) {
859 0 : log_dhcp6_client(client, "%s DUID does not match",
860 : dhcp6_message_type_to_string(message->type));
861 :
862 0 : return -EINVAL;
863 : }
864 3 : clientid = true;
865 :
866 3 : break;
867 :
868 3 : case SD_DHCP6_OPTION_SERVERID:
869 3 : r = dhcp6_lease_get_serverid(lease, NULL, NULL);
870 3 : if (r >= 0) {
871 0 : log_dhcp6_client(client, "%s contains multiple serverids",
872 : dhcp6_message_type_to_string(message->type));
873 0 : return -EINVAL;
874 : }
875 :
876 3 : r = dhcp6_lease_set_serverid(lease, optval, optlen);
877 3 : if (r < 0)
878 0 : return r;
879 :
880 3 : break;
881 :
882 1 : case SD_DHCP6_OPTION_PREFERENCE:
883 1 : if (optlen != 1)
884 0 : return -EINVAL;
885 :
886 1 : r = dhcp6_lease_set_preference(lease, optval[0]);
887 1 : if (r < 0)
888 0 : return r;
889 :
890 1 : break;
891 :
892 0 : case SD_DHCP6_OPTION_STATUS_CODE:
893 0 : status = dhcp6_option_parse_status(option, optlen + sizeof(DHCP6Option));
894 0 : if (status < 0)
895 0 : return status;
896 :
897 0 : if (status > 0) {
898 0 : log_dhcp6_client(client, "%s Status %s",
899 : dhcp6_message_type_to_string(message->type),
900 : dhcp6_message_status_to_string(status));
901 :
902 0 : return -EINVAL;
903 : }
904 :
905 0 : break;
906 :
907 3 : case SD_DHCP6_OPTION_IA_NA:
908 3 : if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
909 1 : log_dhcp6_client(client, "Information request ignoring IA NA option");
910 :
911 1 : break;
912 : }
913 :
914 2 : r = dhcp6_option_parse_ia(option, &lease->ia);
915 2 : if (r < 0 && r != -ENOMSG)
916 0 : return r;
917 :
918 2 : r = dhcp6_lease_get_iaid(lease, &iaid_lease);
919 2 : if (r < 0)
920 0 : return r;
921 :
922 2 : if (client->ia_na.ia_na.id != iaid_lease) {
923 0 : log_dhcp6_client(client, "%s has wrong IAID for IA NA",
924 : dhcp6_message_type_to_string(message->type));
925 0 : return -EINVAL;
926 : }
927 :
928 2 : if (lease->ia.addresses) {
929 2 : lt_t1 = MIN(lt_t1, be32toh(lease->ia.ia_na.lifetime_t1));
930 2 : lt_t2 = MIN(lt_t2, be32toh(lease->ia.ia_na.lifetime_t1));
931 : }
932 :
933 2 : break;
934 :
935 0 : case SD_DHCP6_OPTION_IA_PD:
936 0 : if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
937 0 : log_dhcp6_client(client, "Information request ignoring IA PD option");
938 :
939 0 : break;
940 : }
941 :
942 0 : r = dhcp6_option_parse_ia(option, &lease->pd);
943 0 : if (r < 0 && r != -ENOMSG)
944 0 : return r;
945 :
946 0 : r = dhcp6_lease_get_pd_iaid(lease, &iaid_lease);
947 0 : if (r < 0)
948 0 : return r;
949 :
950 0 : if (client->ia_pd.ia_pd.id != iaid_lease) {
951 0 : log_dhcp6_client(client, "%s has wrong IAID for IA PD",
952 : dhcp6_message_type_to_string(message->type));
953 0 : return -EINVAL;
954 : }
955 :
956 0 : if (lease->pd.addresses) {
957 0 : lt_t1 = MIN(lt_t1, be32toh(lease->pd.ia_pd.lifetime_t1));
958 0 : lt_t2 = MIN(lt_t2, be32toh(lease->pd.ia_pd.lifetime_t2));
959 : }
960 :
961 0 : break;
962 :
963 0 : case SD_DHCP6_OPTION_RAPID_COMMIT:
964 0 : r = dhcp6_lease_set_rapid_commit(lease);
965 0 : if (r < 0)
966 0 : return r;
967 :
968 0 : break;
969 :
970 3 : case SD_DHCP6_OPTION_DNS_SERVERS:
971 3 : r = dhcp6_lease_set_dns(lease, optval, optlen);
972 3 : if (r < 0)
973 0 : return r;
974 :
975 3 : break;
976 :
977 3 : case SD_DHCP6_OPTION_DOMAIN_LIST:
978 3 : r = dhcp6_lease_set_domains(lease, optval, optlen);
979 3 : if (r < 0)
980 0 : return r;
981 :
982 3 : break;
983 :
984 0 : case SD_DHCP6_OPTION_NTP_SERVER:
985 0 : r = dhcp6_lease_set_ntp(lease, optval, optlen);
986 0 : if (r < 0)
987 0 : return r;
988 :
989 0 : break;
990 :
991 3 : case SD_DHCP6_OPTION_SNTP_SERVERS:
992 3 : r = dhcp6_lease_set_sntp(lease, optval, optlen);
993 3 : if (r < 0)
994 0 : return r;
995 :
996 3 : break;
997 : }
998 :
999 19 : pos += offsetof(DHCP6Option, data) + optlen;
1000 : }
1001 :
1002 3 : if (!clientid) {
1003 0 : log_dhcp6_client(client, "%s has incomplete options",
1004 : dhcp6_message_type_to_string(message->type));
1005 0 : return -EINVAL;
1006 : }
1007 :
1008 3 : if (client->state != DHCP6_STATE_INFORMATION_REQUEST) {
1009 2 : r = dhcp6_lease_get_serverid(lease, NULL, NULL);
1010 2 : if (r < 0) {
1011 0 : log_dhcp6_client(client, "%s has no server id",
1012 : dhcp6_message_type_to_string(message->type));
1013 0 : return -EINVAL;
1014 : }
1015 :
1016 : } else {
1017 1 : if (lease->ia.addresses) {
1018 0 : lease->ia.ia_na.lifetime_t1 = htobe32(lt_t1);
1019 0 : lease->ia.ia_na.lifetime_t2 = htobe32(lt_t2);
1020 : }
1021 :
1022 1 : if (lease->pd.addresses) {
1023 0 : lease->pd.ia_pd.lifetime_t1 = htobe32(lt_t1);
1024 0 : lease->pd.ia_pd.lifetime_t2 = htobe32(lt_t2);
1025 : }
1026 : }
1027 :
1028 3 : return 0;
1029 : }
1030 :
1031 3 : static int client_receive_reply(sd_dhcp6_client *client, DHCP6Message *reply, size_t len) {
1032 3 : _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
1033 : bool rapid_commit;
1034 : int r;
1035 :
1036 3 : assert(client);
1037 3 : assert(reply);
1038 :
1039 3 : if (reply->type != DHCP6_REPLY)
1040 1 : return 0;
1041 :
1042 2 : r = dhcp6_lease_new(&lease);
1043 2 : if (r < 0)
1044 0 : return -ENOMEM;
1045 :
1046 2 : r = client_parse_message(client, reply, len, lease);
1047 2 : if (r < 0)
1048 0 : return r;
1049 :
1050 2 : if (client->state == DHCP6_STATE_SOLICITATION) {
1051 0 : r = dhcp6_lease_get_rapid_commit(lease, &rapid_commit);
1052 0 : if (r < 0)
1053 0 : return r;
1054 :
1055 0 : if (!rapid_commit)
1056 0 : return 0;
1057 : }
1058 :
1059 2 : sd_dhcp6_lease_unref(client->lease);
1060 2 : client->lease = TAKE_PTR(lease);
1061 :
1062 2 : return DHCP6_STATE_BOUND;
1063 : }
1064 :
1065 1 : static int client_receive_advertise(sd_dhcp6_client *client, DHCP6Message *advertise, size_t len) {
1066 1 : _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
1067 1 : uint8_t pref_advertise = 0, pref_lease = 0;
1068 : int r;
1069 :
1070 1 : if (advertise->type != DHCP6_ADVERTISE)
1071 0 : return 0;
1072 :
1073 1 : r = dhcp6_lease_new(&lease);
1074 1 : if (r < 0)
1075 0 : return r;
1076 :
1077 1 : r = client_parse_message(client, advertise, len, lease);
1078 1 : if (r < 0)
1079 0 : return r;
1080 :
1081 1 : r = dhcp6_lease_get_preference(lease, &pref_advertise);
1082 1 : if (r < 0)
1083 0 : return r;
1084 :
1085 1 : r = dhcp6_lease_get_preference(client->lease, &pref_lease);
1086 :
1087 1 : if (r < 0 || pref_advertise > pref_lease) {
1088 1 : sd_dhcp6_lease_unref(client->lease);
1089 1 : client->lease = TAKE_PTR(lease);
1090 1 : r = 0;
1091 : }
1092 :
1093 1 : if (pref_advertise == 255 || client->retransmit_count > 1)
1094 0 : r = DHCP6_STATE_REQUEST;
1095 :
1096 1 : return r;
1097 : }
1098 :
1099 3 : static int client_receive_message(
1100 : sd_event_source *s,
1101 : int fd, uint32_t
1102 : revents,
1103 : void *userdata) {
1104 :
1105 3 : sd_dhcp6_client *client = userdata;
1106 6 : DHCP6_CLIENT_DONT_DESTROY(client);
1107 3 : _cleanup_free_ DHCP6Message *message = NULL;
1108 : ssize_t buflen, len;
1109 3 : int r = 0;
1110 :
1111 3 : assert(s);
1112 3 : assert(client);
1113 3 : assert(client->event);
1114 :
1115 3 : buflen = next_datagram_size_fd(fd);
1116 3 : if (buflen == -ENETDOWN) {
1117 : /* the link is down. Don't return an error or the I/O event
1118 : source will be disconnected and we won't be able to receive
1119 : packets again when the link comes back. */
1120 0 : return 0;
1121 : }
1122 3 : if (buflen < 0)
1123 0 : return buflen;
1124 :
1125 3 : message = malloc(buflen);
1126 3 : if (!message)
1127 0 : return -ENOMEM;
1128 :
1129 3 : len = recv(fd, message, buflen, 0);
1130 3 : if (len < 0) {
1131 : /* see comment above for why we shouldn't error out on ENETDOWN. */
1132 0 : if (IN_SET(errno, EAGAIN, EINTR, ENETDOWN))
1133 0 : return 0;
1134 :
1135 0 : return log_dhcp6_client_errno(client, errno, "Could not receive message from UDP socket: %m");
1136 :
1137 : }
1138 3 : if ((size_t) len < sizeof(DHCP6Message)) {
1139 0 : log_dhcp6_client(client, "Too small to be DHCP6 message: ignoring");
1140 0 : return 0;
1141 : }
1142 :
1143 3 : switch(message->type) {
1144 0 : case DHCP6_SOLICIT:
1145 : case DHCP6_REQUEST:
1146 : case DHCP6_CONFIRM:
1147 : case DHCP6_RENEW:
1148 : case DHCP6_REBIND:
1149 : case DHCP6_RELEASE:
1150 : case DHCP6_DECLINE:
1151 : case DHCP6_INFORMATION_REQUEST:
1152 : case DHCP6_RELAY_FORW:
1153 : case DHCP6_RELAY_REPL:
1154 0 : return 0;
1155 :
1156 3 : case DHCP6_ADVERTISE:
1157 : case DHCP6_REPLY:
1158 : case DHCP6_RECONFIGURE:
1159 3 : break;
1160 :
1161 0 : default:
1162 0 : log_dhcp6_client(client, "Unknown message type %d", message->type);
1163 0 : return 0;
1164 : }
1165 :
1166 3 : if (client->transaction_id != (message->transaction_id &
1167 3 : htobe32(0x00ffffff)))
1168 0 : return 0;
1169 :
1170 3 : switch (client->state) {
1171 1 : case DHCP6_STATE_INFORMATION_REQUEST:
1172 1 : r = client_receive_reply(client, message, len);
1173 1 : if (r < 0)
1174 0 : return 0;
1175 :
1176 1 : client_notify(client, SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST);
1177 :
1178 1 : client_start(client, DHCP6_STATE_STOPPED);
1179 :
1180 1 : break;
1181 :
1182 1 : case DHCP6_STATE_SOLICITATION:
1183 1 : r = client_receive_advertise(client, message, len);
1184 :
1185 1 : if (r == DHCP6_STATE_REQUEST) {
1186 0 : client_start(client, r);
1187 :
1188 0 : break;
1189 : }
1190 :
1191 : _fallthrough_; /* for Soliciation Rapid Commit option check */
1192 : case DHCP6_STATE_REQUEST:
1193 : case DHCP6_STATE_RENEW:
1194 : case DHCP6_STATE_REBIND:
1195 :
1196 2 : r = client_receive_reply(client, message, len);
1197 2 : if (r < 0)
1198 0 : return 0;
1199 :
1200 2 : if (r == DHCP6_STATE_BOUND) {
1201 :
1202 1 : r = client_start(client, DHCP6_STATE_BOUND);
1203 1 : if (r < 0) {
1204 0 : client_stop(client, r);
1205 0 : return 0;
1206 : }
1207 :
1208 1 : client_notify(client, SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE);
1209 : }
1210 :
1211 2 : break;
1212 :
1213 0 : case DHCP6_STATE_BOUND:
1214 :
1215 0 : break;
1216 :
1217 0 : case DHCP6_STATE_STOPPED:
1218 0 : return 0;
1219 : }
1220 :
1221 3 : log_dhcp6_client(client, "Recv %s",
1222 : dhcp6_message_type_to_string(message->type));
1223 :
1224 3 : return 0;
1225 : }
1226 :
1227 1 : static int client_get_lifetime(sd_dhcp6_client *client, uint32_t *lifetime_t1,
1228 : uint32_t *lifetime_t2) {
1229 1 : assert_return(client, -EINVAL);
1230 1 : assert_return(client->lease, -EINVAL);
1231 :
1232 1 : if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA) && client->lease->ia.addresses) {
1233 1 : *lifetime_t1 = be32toh(client->lease->ia.ia_na.lifetime_t1);
1234 1 : *lifetime_t2 = be32toh(client->lease->ia.ia_na.lifetime_t2);
1235 :
1236 1 : return 0;
1237 : }
1238 :
1239 0 : if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD) && client->lease->pd.addresses) {
1240 0 : *lifetime_t1 = be32toh(client->lease->pd.ia_pd.lifetime_t1);
1241 0 : *lifetime_t2 = be32toh(client->lease->pd.ia_pd.lifetime_t2);
1242 :
1243 0 : return 0;
1244 : }
1245 :
1246 0 : return -ENOMSG;
1247 : }
1248 :
1249 5 : static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
1250 : int r;
1251 : usec_t timeout, time_now;
1252 : char time_string[FORMAT_TIMESPAN_MAX];
1253 : uint32_t lifetime_t1, lifetime_t2;
1254 :
1255 5 : assert_return(client, -EINVAL);
1256 5 : assert_return(client->event, -EINVAL);
1257 5 : assert_return(client->ifindex > 0, -EINVAL);
1258 5 : assert_return(client->state != state, -EINVAL);
1259 :
1260 5 : (void) event_source_disable(client->timeout_resend_expire);
1261 5 : (void) event_source_disable(client->timeout_resend);
1262 5 : client->retransmit_time = 0;
1263 5 : client->retransmit_count = 0;
1264 :
1265 5 : r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
1266 5 : if (r < 0)
1267 0 : return r;
1268 :
1269 5 : if (!client->receive_message) {
1270 2 : r = sd_event_add_io(client->event, &client->receive_message,
1271 : client->fd, EPOLLIN, client_receive_message,
1272 : client);
1273 2 : if (r < 0)
1274 0 : goto error;
1275 :
1276 2 : r = sd_event_source_set_priority(client->receive_message,
1277 2 : client->event_priority);
1278 2 : if (r < 0)
1279 0 : goto error;
1280 :
1281 2 : r = sd_event_source_set_description(client->receive_message,
1282 : "dhcp6-receive-message");
1283 2 : if (r < 0)
1284 0 : goto error;
1285 : }
1286 :
1287 5 : switch (state) {
1288 1 : case DHCP6_STATE_STOPPED:
1289 1 : if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
1290 0 : client->state = DHCP6_STATE_STOPPED;
1291 :
1292 0 : return 0;
1293 : }
1294 :
1295 : _fallthrough_;
1296 : case DHCP6_STATE_SOLICITATION:
1297 2 : client->state = DHCP6_STATE_SOLICITATION;
1298 :
1299 2 : break;
1300 :
1301 2 : case DHCP6_STATE_INFORMATION_REQUEST:
1302 : case DHCP6_STATE_REQUEST:
1303 : case DHCP6_STATE_RENEW:
1304 : case DHCP6_STATE_REBIND:
1305 :
1306 2 : client->state = state;
1307 :
1308 2 : break;
1309 :
1310 1 : case DHCP6_STATE_BOUND:
1311 :
1312 1 : r = client_get_lifetime(client, &lifetime_t1, &lifetime_t2);
1313 1 : if (r < 0)
1314 0 : goto error;
1315 :
1316 1 : if (lifetime_t1 == 0xffffffff || lifetime_t2 == 0xffffffff) {
1317 0 : log_dhcp6_client(client, "Infinite T1 0x%08x or T2 0x%08x",
1318 : lifetime_t1, lifetime_t2);
1319 :
1320 0 : return 0;
1321 : }
1322 :
1323 1 : timeout = client_timeout_compute_random(lifetime_t1 * USEC_PER_SEC);
1324 :
1325 1 : log_dhcp6_client(client, "T1 expires in %s",
1326 : format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC));
1327 :
1328 1 : r = event_reset_time(client->event, &client->timeout_t1,
1329 : clock_boottime_or_monotonic(),
1330 : time_now + timeout, 10 * USEC_PER_SEC,
1331 : client_timeout_t1, client,
1332 1 : client->event_priority, "dhcp6-t1-timeout", true);
1333 1 : if (r < 0)
1334 0 : goto error;
1335 :
1336 1 : timeout = client_timeout_compute_random(lifetime_t2 * USEC_PER_SEC);
1337 :
1338 1 : log_dhcp6_client(client, "T2 expires in %s",
1339 : format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC));
1340 :
1341 1 : r = event_reset_time(client->event, &client->timeout_t2,
1342 : clock_boottime_or_monotonic(),
1343 : time_now + timeout, 10 * USEC_PER_SEC,
1344 : client_timeout_t2, client,
1345 1 : client->event_priority, "dhcp6-t2-timeout", true);
1346 1 : if (r < 0)
1347 0 : goto error;
1348 :
1349 1 : client->state = state;
1350 :
1351 1 : return 0;
1352 : }
1353 :
1354 4 : client->transaction_id = random_u32() & htobe32(0x00ffffff);
1355 4 : client->transaction_start = time_now;
1356 :
1357 4 : r = event_reset_time(client->event, &client->timeout_resend,
1358 : clock_boottime_or_monotonic(),
1359 : 0, 0,
1360 : client_timeout_resend, client,
1361 4 : client->event_priority, "dhcp6-resend-timeout", true);
1362 4 : if (r < 0)
1363 0 : goto error;
1364 :
1365 4 : return 0;
1366 :
1367 0 : error:
1368 0 : client_reset(client);
1369 0 : return r;
1370 : }
1371 :
1372 1 : int sd_dhcp6_client_stop(sd_dhcp6_client *client) {
1373 1 : assert_return(client, -EINVAL);
1374 :
1375 1 : client_stop(client, SD_DHCP6_CLIENT_EVENT_STOP);
1376 :
1377 1 : client->fd = safe_close(client->fd);
1378 :
1379 1 : return 0;
1380 : }
1381 :
1382 0 : int sd_dhcp6_client_is_running(sd_dhcp6_client *client) {
1383 0 : assert_return(client, -EINVAL);
1384 :
1385 0 : return client->state != DHCP6_STATE_STOPPED;
1386 : }
1387 :
1388 2 : int sd_dhcp6_client_start(sd_dhcp6_client *client) {
1389 2 : enum DHCP6State state = DHCP6_STATE_SOLICITATION;
1390 2 : int r = 0;
1391 :
1392 2 : assert_return(client, -EINVAL);
1393 2 : assert_return(client->event, -EINVAL);
1394 2 : assert_return(client->ifindex > 0, -EINVAL);
1395 2 : assert_return(in_addr_is_link_local(AF_INET6, (const union in_addr_union *) &client->local_address) > 0, -EINVAL);
1396 :
1397 2 : if (!IN_SET(client->state, DHCP6_STATE_STOPPED))
1398 0 : return -EBUSY;
1399 :
1400 2 : if (!client->information_request && !client->request)
1401 0 : return -EINVAL;
1402 :
1403 2 : r = client_reset(client);
1404 2 : if (r < 0)
1405 0 : return r;
1406 :
1407 2 : r = client_ensure_iaid(client);
1408 2 : if (r < 0)
1409 0 : return r;
1410 :
1411 2 : r = client_ensure_duid(client);
1412 2 : if (r < 0)
1413 0 : return r;
1414 :
1415 2 : if (client->fd < 0) {
1416 2 : r = dhcp6_network_bind_udp_socket(client->ifindex, &client->local_address);
1417 2 : if (r < 0) {
1418 0 : _cleanup_free_ char *p = NULL;
1419 :
1420 0 : (void) in_addr_to_string(AF_INET6, (const union in_addr_union*) &client->local_address, &p);
1421 0 : return log_dhcp6_client_errno(client, r,
1422 : "Failed to bind to UDP socket at address %s: %m", strna(p));
1423 : }
1424 :
1425 2 : client->fd = r;
1426 : }
1427 :
1428 2 : if (client->information_request)
1429 1 : state = DHCP6_STATE_INFORMATION_REQUEST;
1430 :
1431 2 : log_dhcp6_client(client, "Started in %s mode",
1432 : client->information_request? "Information request":
1433 : "Managed");
1434 :
1435 2 : return client_start(client, state);
1436 : }
1437 :
1438 2 : int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, int64_t priority) {
1439 : int r;
1440 :
1441 2 : assert_return(client, -EINVAL);
1442 2 : assert_return(!client->event, -EBUSY);
1443 :
1444 2 : if (event)
1445 2 : client->event = sd_event_ref(event);
1446 : else {
1447 0 : r = sd_event_default(&client->event);
1448 0 : if (r < 0)
1449 0 : return 0;
1450 : }
1451 :
1452 2 : client->event_priority = priority;
1453 :
1454 2 : return 0;
1455 : }
1456 :
1457 3 : int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) {
1458 3 : assert_return(client, -EINVAL);
1459 :
1460 3 : client->event = sd_event_unref(client->event);
1461 :
1462 3 : return 0;
1463 : }
1464 :
1465 0 : sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
1466 0 : assert_return(client, NULL);
1467 :
1468 0 : return client->event;
1469 : }
1470 :
1471 2 : static sd_dhcp6_client *dhcp6_client_free(sd_dhcp6_client *client) {
1472 2 : assert(client);
1473 :
1474 2 : client->timeout_resend = sd_event_source_unref(client->timeout_resend);
1475 2 : client->timeout_resend_expire = sd_event_source_unref(client->timeout_resend_expire);
1476 2 : client->timeout_t1 = sd_event_source_unref(client->timeout_t1);
1477 2 : client->timeout_t2 = sd_event_source_unref(client->timeout_t2);
1478 :
1479 2 : client_reset(client);
1480 :
1481 2 : client->fd = safe_close(client->fd);
1482 :
1483 2 : sd_dhcp6_client_detach_event(client);
1484 :
1485 2 : free(client->req_opts);
1486 2 : free(client->fqdn);
1487 2 : return mfree(client);
1488 : }
1489 :
1490 16 : DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_client, sd_dhcp6_client, dhcp6_client_free);
1491 :
1492 2 : int sd_dhcp6_client_new(sd_dhcp6_client **ret) {
1493 2 : _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL;
1494 2 : _cleanup_free_ be16_t *req_opts = NULL;
1495 : size_t t;
1496 :
1497 2 : assert_return(ret, -EINVAL);
1498 :
1499 2 : req_opts = new(be16_t, ELEMENTSOF(default_req_opts));
1500 2 : if (!req_opts)
1501 0 : return -ENOMEM;
1502 :
1503 10 : for (t = 0; t < ELEMENTSOF(default_req_opts); t++)
1504 8 : req_opts[t] = htobe16(default_req_opts[t]);
1505 :
1506 2 : client = new(sd_dhcp6_client, 1);
1507 2 : if (!client)
1508 0 : return -ENOMEM;
1509 :
1510 4 : *client = (sd_dhcp6_client) {
1511 : .n_ref = 1,
1512 : .ia_na.type = SD_DHCP6_OPTION_IA_NA,
1513 : .ia_pd.type = SD_DHCP6_OPTION_IA_PD,
1514 : .ifindex = -1,
1515 : .request = DHCP6_REQUEST_IA_NA,
1516 : .fd = -1,
1517 : .req_opts_len = ELEMENTSOF(default_req_opts),
1518 2 : .req_opts = TAKE_PTR(req_opts),
1519 : };
1520 :
1521 2 : *ret = TAKE_PTR(client);
1522 :
1523 2 : return 0;
1524 : }
|