]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/sd-dhcp6-client.c
Adding dhcp client and dhcp6 client state interface
[thirdparty/systemd.git] / src / libsystemd-network / sd-dhcp6-client.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
139b011a 2/***
810adae9 3 Copyright © 2014-2015 Intel Corporation. All rights reserved.
139b011a
PF
4***/
5
6#include <errno.h>
631bbe71 7#include <sys/ioctl.h>
2af02e61
DDM
8#include <net/if.h>
9#include <linux/if_arp.h> /* Must be included after <net/if.h> */
10#include <linux/if_infiniband.h> /* Must be included after <net/if.h> */
139b011a 11
139b011a 12#include "sd-dhcp6-client.h"
07630cea 13
b5efdb8a 14#include "alloc-util.h"
793178b9 15#include "device-util.h"
07630cea 16#include "dhcp-identifier.h"
f12abb48 17#include "dhcp6-internal.h"
631bbe71 18#include "dhcp6-lease-internal.h"
8006aa32 19#include "dns-domain.h"
c9393e8c 20#include "event-util.h"
3ffd4af2 21#include "fd-util.h"
6b7d5b6e 22#include "hexdecoct.h"
8006aa32 23#include "hostname-util.h"
c601ebf7 24#include "in-addr-util.h"
653ddc1d 25#include "io-util.h"
07630cea 26#include "random-util.h"
4edc2c9b 27#include "socket-util.h"
0e0c4dae 28#include "sort-util.h"
7e19cc54 29#include "strv.h"
de8d6e55 30#include "web-util.h"
139b011a 31
3f0c075f 32#define DHCP6_CLIENT_DONT_DESTROY(client) \
4afd3348 33 _cleanup_(sd_dhcp6_client_unrefp) _unused_ sd_dhcp6_client *_dont_destroy_##client = sd_dhcp6_client_ref(client)
3f0c075f 34
e5d69be2 35static int client_start_transaction(sd_dhcp6_client *client, DHCP6State state);
c3e2adea 36
4b558378
ZJS
37int sd_dhcp6_client_set_callback(
38 sd_dhcp6_client *client,
39 sd_dhcp6_client_callback_t cb,
40 void *userdata) {
45aa74c7 41
139b011a
PF
42 assert_return(client, -EINVAL);
43
45aa74c7 44 client->callback = cb;
139b011a
PF
45 client->userdata = userdata;
46
47 return 0;
48}
49
fd9b7f5b 50int dhcp6_client_set_state_callback(
51 sd_dhcp6_client *client,
52 sd_dhcp6_client_callback_t cb,
53 void *userdata) {
54
55 assert_return(client, -EINVAL);
56
57 client->state_callback = cb;
58 client->state_userdata = userdata;
59
60 return 0;
61}
62
2f8e7633 63int sd_dhcp6_client_set_ifindex(sd_dhcp6_client *client, int ifindex) {
2f8e7633 64 assert_return(client, -EINVAL);
6f4490bb 65 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
7fa69c0a 66 assert_return(ifindex > 0, -EINVAL);
d7c9c21f 67
2f8e7633 68 client->ifindex = ifindex;
139b011a
PF
69 return 0;
70}
71
61a9fa8f
YW
72int sd_dhcp6_client_set_ifname(sd_dhcp6_client *client, const char *ifname) {
73 assert_return(client, -EINVAL);
74 assert_return(ifname, -EINVAL);
75
76 if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE))
77 return -EINVAL;
78
79 return free_and_strdup(&client->ifname, ifname);
80}
81
5977b71f
YW
82int sd_dhcp6_client_get_ifname(sd_dhcp6_client *client, const char **ret) {
83 int r;
61a9fa8f 84
5977b71f
YW
85 assert_return(client, -EINVAL);
86
87 r = get_ifname(client->ifindex, &client->ifname);
88 if (r < 0)
89 return r;
90
91 if (ret)
92 *ret = client->ifname;
93
94 return 0;
61a9fa8f
YW
95}
96
4b558378
ZJS
97int sd_dhcp6_client_set_local_address(
98 sd_dhcp6_client *client,
99 const struct in6_addr *local_address) {
100
c601ebf7 101 assert_return(client, -EINVAL);
6f4490bb 102 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
c601ebf7 103 assert_return(local_address, -EINVAL);
94876904 104 assert_return(in6_addr_is_link_local(local_address) > 0, -EINVAL);
c601ebf7
TG
105
106 client->local_address = *local_address;
107
108 return 0;
109}
110
0ae0e5cd
LP
111int sd_dhcp6_client_set_mac(
112 sd_dhcp6_client *client,
1978efb9
YW
113 const uint8_t *addr,
114 size_t addr_len,
0ae0e5cd
LP
115 uint16_t arp_type) {
116
139b011a 117 assert_return(client, -EINVAL);
76253e73 118 assert_return(addr, -EINVAL);
1978efb9 119 assert_return(addr_len <= sizeof(client->hw_addr.bytes), -EINVAL);
6f4490bb
YW
120
121 /* Unlike the other setters, it is OK to set a new MAC address while the client is running,
122 * as the MAC address is used only when setting DUID or IAID. */
d7c9c21f 123
76253e73
DW
124 if (arp_type == ARPHRD_ETHER)
125 assert_return(addr_len == ETH_ALEN, -EINVAL);
126 else if (arp_type == ARPHRD_INFINIBAND)
127 assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
1d370b2c
JT
128 else {
129 client->arp_type = ARPHRD_NONE;
1978efb9 130 client->hw_addr.length = 0;
1d370b2c
JT
131 return 0;
132 }
76253e73 133
76253e73 134 client->arp_type = arp_type;
e0ead130 135 hw_addr_set(&client->hw_addr, addr, addr_len);
139b011a
PF
136
137 return 0;
138}
139
2805536b
SS
140int sd_dhcp6_client_set_prefix_delegation_hint(
141 sd_dhcp6_client *client,
142 uint8_t prefixlen,
877bfc78
YW
143 const struct in6_addr *pd_prefix) {
144
145 _cleanup_free_ DHCP6Address *prefix = NULL;
2805536b
SS
146
147 assert_return(client, -EINVAL);
6f4490bb 148 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
2805536b 149
877bfc78
YW
150 if (!pd_prefix) {
151 /* clear previous assignments. */
152 dhcp6_ia_clear_addresses(&client->ia_pd);
153 return 0;
154 }
155
156 assert_return(prefixlen > 0 && prefixlen <= 128, -EINVAL);
2805536b 157
877bfc78
YW
158 prefix = new(DHCP6Address, 1);
159 if (!prefix)
160 return -ENOMEM;
161
162 *prefix = (DHCP6Address) {
163 .iapdprefix.address = *pd_prefix,
164 .iapdprefix.prefixlen = prefixlen,
165 };
166
167 LIST_PREPEND(addresses, client->ia_pd.addresses, TAKE_PTR(prefix));
168 return 1;
2805536b
SS
169}
170
99ccb8ff
SS
171int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client, sd_dhcp6_option *v) {
172 int r;
173
174 assert_return(client, -EINVAL);
6f4490bb 175 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
99ccb8ff 176
9e4dee4c
YW
177 if (!v) {
178 /* Clear the previous assignments. */
179 ordered_set_clear(client->vendor_options);
180 return 0;
181 }
182
183 r = ordered_set_ensure_put(&client->vendor_options, &dhcp6_option_hash_ops, v);
99ccb8ff
SS
184 if (r < 0)
185 return r;
186
187 sd_dhcp6_option_ref(v);
188
189 return 1;
190}
191
0ae0e5cd 192static int client_ensure_duid(sd_dhcp6_client *client) {
6f4490bb
YW
193 assert(client);
194
cc22955c
TH
195 if (client->duid_len != 0)
196 return 0;
0ae0e5cd 197
ac680f76 198 return dhcp_identifier_set_duid_en(client->test_mode, &client->duid, &client->duid_len);
cc22955c
TH
199}
200
d7df2fd3
ZJS
201/**
202 * Sets DUID. If duid is non-null, the DUID is set to duid_type + duid
203 * without further modification. Otherwise, if duid_type is supported, DUID
204 * is set based on that type. Otherwise, an error is returned.
205 */
53488ea3 206int sd_dhcp6_client_set_duid_llt(sd_dhcp6_client *client, uint64_t llt_time) {
413708d1 207 int r;
27eba50e 208
66eac120 209 assert_return(client, -EINVAL);
6f4490bb 210 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
c83321e6 211
53488ea3
YW
212 r = dhcp_identifier_set_duid_llt(&client->hw_addr, client->arp_type, llt_time, &client->duid, &client->duid_len);
213 if (r < 0)
214 return log_dhcp6_client_errno(client, r, "Failed to set DUID-LLT: %m");
01bcea49 215
53488ea3
YW
216 return 0;
217}
d7df2fd3 218
53488ea3
YW
219int sd_dhcp6_client_set_duid_ll(sd_dhcp6_client *client) {
220 int r;
6ed69be9 221
53488ea3
YW
222 assert_return(client, -EINVAL);
223 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
224
225 r = dhcp_identifier_set_duid_ll(&client->hw_addr, client->arp_type, &client->duid, &client->duid_len);
226 if (r < 0)
227 return log_dhcp6_client_errno(client, r, "Failed to set DUID-LL: %m");
fe4b2156 228
413708d1
VK
229 return 0;
230}
231
53488ea3
YW
232int sd_dhcp6_client_set_duid_en(sd_dhcp6_client *client) {
233 int r;
234
235 assert_return(client, -EINVAL);
236 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
237
238 r = dhcp_identifier_set_duid_en(client->test_mode, &client->duid, &client->duid_len);
239 if (r < 0)
240 return log_dhcp6_client_errno(client, r, "Failed to set DUID-EN: %m");
241
242 return 0;
7e90a499
YW
243}
244
53488ea3
YW
245int sd_dhcp6_client_set_duid_uuid(sd_dhcp6_client *client) {
246 int r;
247
248 assert_return(client, -EINVAL);
249 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
250
251 r = dhcp_identifier_set_duid_uuid(&client->duid, &client->duid_len);
252 if (r < 0)
253 return log_dhcp6_client_errno(client, r, "Failed to set DUID-UUID: %m");
254
255 return 0;
256}
257
258int sd_dhcp6_client_set_duid_raw(sd_dhcp6_client *client, uint16_t duid_type, const uint8_t *duid, size_t duid_len) {
259 int r;
260
261 assert_return(client, -EINVAL);
262 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
263 assert_return(duid || duid_len == 0, -EINVAL);
264
265 r = dhcp_identifier_set_duid_raw(duid_type, duid, duid_len, &client->duid, &client->duid_len);
266 if (r < 0)
267 return log_dhcp6_client_errno(client, r, "Failed to set DUID: %m");
268
269 return 0;
7e90a499
YW
270}
271
6b7d5b6e
SS
272int sd_dhcp6_client_duid_as_string(
273 sd_dhcp6_client *client,
274 char **duid) {
275 _cleanup_free_ char *p = NULL, *s = NULL, *t = NULL;
276 const char *v;
277 int r;
278
279 assert_return(client, -EINVAL);
280 assert_return(client->duid_len > 0, -ENODATA);
361eb412 281 assert_return(duid, -EINVAL);
6b7d5b6e 282
f9971018 283 v = duid_type_to_string(be16toh(client->duid.type));
6b7d5b6e
SS
284 if (v) {
285 s = strdup(v);
286 if (!s)
287 return -ENOMEM;
288 } else {
289 r = asprintf(&s, "%0x", client->duid.type);
290 if (r < 0)
291 return -ENOMEM;
292 }
293
294 t = hexmem(&client->duid.raw.data, client->duid_len);
295 if (!t)
296 return -ENOMEM;
297
298 p = strjoin(s, ":", t);
299 if (!p)
300 return -ENOMEM;
301
302 *duid = TAKE_PTR(p);
303
304 return 0;
305}
306
413708d1
VK
307int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) {
308 assert_return(client, -EINVAL);
6f4490bb 309 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
413708d1 310
4b0f2717
YW
311 client->ia_na.header.id = htobe32(iaid);
312 client->ia_pd.header.id = htobe32(iaid);
8217ed5e 313 client->iaid_set = true;
66eac120
DW
314
315 return 0;
316}
317
0eca25ba
YW
318static int client_ensure_iaid(sd_dhcp6_client *client) {
319 int r;
320 uint32_t iaid;
321
8d71f2b3
YW
322 assert(client);
323
0eca25ba
YW
324 if (client->iaid_set)
325 return 0;
326
14805b14 327 r = dhcp_identifier_set_iaid(client->dev, &client->hw_addr,
0eca25ba 328 /* legacy_unstable_byteorder = */ true,
0eca25ba
YW
329 &iaid);
330 if (r < 0)
331 return r;
332
333 client->ia_na.header.id = iaid;
334 client->ia_pd.header.id = iaid;
335 client->iaid_set = true;
336
337 return 0;
8d71f2b3
YW
338}
339
d69d4038
SS
340int sd_dhcp6_client_get_iaid(sd_dhcp6_client *client, uint32_t *iaid) {
341 assert_return(client, -EINVAL);
342 assert_return(iaid, -EINVAL);
343
344 if (!client->iaid_set)
345 return -ENODATA;
346
4b0f2717 347 *iaid = be32toh(client->ia_na.header.id);
d69d4038
SS
348
349 return 0;
350}
351
0eca25ba
YW
352void dhcp6_client_set_test_mode(sd_dhcp6_client *client, bool test_mode) {
353 assert(client);
354
355 client->test_mode = test_mode;
356}
357
8006aa32
SA
358int sd_dhcp6_client_set_fqdn(
359 sd_dhcp6_client *client,
360 const char *fqdn) {
361
362 assert_return(client, -EINVAL);
6f4490bb 363 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
8006aa32
SA
364
365 /* Make sure FQDN qualifies as DNS and as Linux hostname */
366 if (fqdn &&
52ef5dd7 367 !(hostname_is_valid(fqdn, 0) && dns_name_is_valid(fqdn) > 0))
8006aa32
SA
368 return -EINVAL;
369
370 return free_and_strdup(&client->fqdn, fqdn);
371}
372
04c01369 373int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, int enabled) {
bbfa43ca 374 assert_return(client, -EINVAL);
6f4490bb 375 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
d7c9c21f 376
bbfa43ca
PF
377 client->information_request = enabled;
378
379 return 0;
380}
381
04c01369 382int sd_dhcp6_client_get_information_request(sd_dhcp6_client *client, int *enabled) {
bbfa43ca
PF
383 assert_return(client, -EINVAL);
384 assert_return(enabled, -EINVAL);
385
386 *enabled = client->information_request;
387
388 return 0;
389}
390
0e0c4dae
YW
391static int be16_compare_func(const be16_t *a, const be16_t *b) {
392 return CMP(be16toh(*a), be16toh(*b));
393}
394
0ae0e5cd 395int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, uint16_t option) {
0e0c4dae 396 be16_t opt;
da6fe470
PF
397
398 assert_return(client, -EINVAL);
6f4490bb 399 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
da6fe470 400
fea8c180 401 if (!dhcp6_option_can_request(option))
da6fe470 402 return -EINVAL;
da6fe470 403
0e0c4dae
YW
404 opt = htobe16(option);
405 if (typesafe_bsearch(&opt, client->req_opts, client->n_req_opts, be16_compare_func))
406 return -EEXIST;
da6fe470 407
2f53b311 408 if (!GREEDY_REALLOC(client->req_opts, client->n_req_opts + 1))
da6fe470
PF
409 return -ENOMEM;
410
0e0c4dae 411 client->req_opts[client->n_req_opts++] = opt;
da6fe470 412
0e0c4dae
YW
413 /* Sort immediately to make the above binary search will work for the next time. */
414 typesafe_qsort(client->req_opts, client->n_req_opts, be16_compare_func);
da6fe470
PF
415 return 0;
416}
417
feb7d7a2 418int sd_dhcp6_client_set_request_mud_url(sd_dhcp6_client *client, const char *mudurl) {
de8d6e55 419 assert_return(client, -EINVAL);
6f4490bb 420 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
de8d6e55 421 assert_return(mudurl, -EINVAL);
2d3adfa6 422 assert_return(strlen(mudurl) <= UINT8_MAX, -EINVAL);
de8d6e55
SS
423 assert_return(http_url_is_valid(mudurl), -EINVAL);
424
425 return free_and_strdup(&client->mudurl, mudurl);
426}
427
5a99444e 428int sd_dhcp6_client_set_request_user_class(sd_dhcp6_client *client, char * const *user_class) {
5a99444e 429 char **s;
33923925
SS
430
431 assert_return(client, -EINVAL);
6f4490bb 432 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
5a99444e 433 assert_return(!strv_isempty(user_class), -EINVAL);
73c8ced7 434
5a99444e
YW
435 STRV_FOREACH(p, user_class) {
436 size_t len = strlen(*p);
33923925 437
5a99444e
YW
438 if (len > UINT16_MAX || len == 0)
439 return -EINVAL;
440 }
33923925 441
2d3adfa6 442 s = strv_copy(user_class);
33923925
SS
443 if (!s)
444 return -ENOMEM;
445
5a99444e 446 return strv_free_and_replace(client->user_class, s);
33923925
SS
447}
448
019951ec 449int sd_dhcp6_client_set_request_vendor_class(sd_dhcp6_client *client, char * const *vendor_class) {
019951ec 450 char **s;
73c8ced7
SS
451
452 assert_return(client, -EINVAL);
6f4490bb 453 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
019951ec 454 assert_return(!strv_isempty(vendor_class), -EINVAL);
73c8ced7 455
019951ec
YW
456 STRV_FOREACH(p, vendor_class) {
457 size_t len = strlen(*p);
458
459 if (len > UINT16_MAX || len == 0)
460 return -EINVAL;
461 }
73c8ced7
SS
462
463 s = strv_copy(vendor_class);
464 if (!s)
465 return -ENOMEM;
466
019951ec 467 return strv_free_and_replace(client->vendor_class, s);
73c8ced7
SS
468}
469
d8c51121
PF
470int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client *client, int *delegation) {
471 assert_return(client, -EINVAL);
472 assert_return(delegation, -EINVAL);
473
b261b5f4 474 *delegation = FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_PD);
d8c51121
PF
475
476 return 0;
477}
478
479int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client, int delegation) {
7c3de8f8 480 assert_return(client, -EINVAL);
6f4490bb 481 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
7c3de8f8 482
b261b5f4 483 SET_FLAG(client->request_ia, DHCP6_REQUEST_IA_PD, delegation);
f311a62b
PF
484
485 return 0;
486}
487
488int sd_dhcp6_client_get_address_request(sd_dhcp6_client *client, int *request) {
489 assert_return(client, -EINVAL);
490 assert_return(request, -EINVAL);
491
b261b5f4 492 *request = FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_NA);
f311a62b
PF
493
494 return 0;
495}
496
497int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client, int request) {
498 assert_return(client, -EINVAL);
6f4490bb 499 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
f311a62b 500
b261b5f4 501 SET_FLAG(client->request_ia, DHCP6_REQUEST_IA_NA, request);
7c3de8f8
PF
502
503 return 0;
504}
505
6f3fc861
YW
506int dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id) {
507 assert(client);
508 assert(client->test_mode);
509
510 /* This is for tests or fuzzers. */
d89a400e 511
6f3fc861 512 client->transaction_id = transaction_id & htobe32(0x00ffffff);
d89a400e
EV
513
514 return 0;
515}
516
4397967f
YW
517int sd_dhcp6_client_set_rapid_commit(sd_dhcp6_client *client, int enable) {
518 assert_return(client, -EINVAL);
519 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
520
521 client->rapid_commit = enable;
522 return 0;
523}
524
b895aa5f 525int sd_dhcp6_client_set_send_release(sd_dhcp6_client *client, int enable) {
526 assert_return(client, -EINVAL);
527 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
528
529 client->send_release = enable;
530 return 0;
531}
532
ea3b3a75
PF
533int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
534 assert_return(client, -EINVAL);
ea3b3a75
PF
535
536 if (!client->lease)
537 return -ENOMSG;
538
3098562c
TG
539 if (ret)
540 *ret = client->lease;
ea3b3a75
PF
541
542 return 0;
543}
544
e7d5fe17
AD
545int sd_dhcp6_client_add_option(sd_dhcp6_client *client, sd_dhcp6_option *v) {
546 int r;
547
548 assert_return(client, -EINVAL);
549 assert_return(v, -EINVAL);
550
02288f3e 551 r = ordered_hashmap_ensure_put(&client->extra_options, &dhcp6_option_hash_ops, UINT_TO_PTR(v->option), v);
e7d5fe17
AD
552 if (r < 0)
553 return r;
554
555 sd_dhcp6_option_ref(v);
556 return 0;
557}
558
65b85f23
YW
559static void client_set_state(sd_dhcp6_client *client, DHCP6State state) {
560 assert(client);
561
562 if (client->state == state)
563 return;
564
565 log_dhcp6_client(client, "State changed: %s -> %s",
566 dhcp6_state_to_string(client->state), dhcp6_state_to_string(state));
567
568 client->state = state;
fd9b7f5b 569
570 if (client->state_callback)
571 client->state_callback(client, state, client->state_userdata);
572}
573
574int dhcp6_client_get_state(sd_dhcp6_client *client) {
575 assert_return(client, -EINVAL);
576
577 return client->state;
65b85f23
YW
578}
579
3f0c075f 580static void client_notify(sd_dhcp6_client *client, int event) {
45aa74c7
LP
581 assert(client);
582
583 if (client->callback)
584 client->callback(client, event, client->userdata);
139b011a
PF
585}
586
1929c1fc 587static void client_cleanup(sd_dhcp6_client *client) {
af2b4841 588 assert(client);
4e3e6679 589
af2b4841 590 client->lease = sd_dhcp6_lease_unref(client->lease);
d1b0afe3 591
dd73db78
YW
592 /* Reset IRT here. Otherwise, we cannot restart the client in the information requesting mode,
593 * even though the lease is freed below. */
594 client->information_request_time_usec = 0;
595 client->information_refresh_time_usec = 0;
596
8ef959cd 597 (void) event_source_disable(client->receive_message);
c9393e8c 598 (void) event_source_disable(client->timeout_resend);
3bb18e70 599 (void) event_source_disable(client->timeout_expire);
c9393e8c
YW
600 (void) event_source_disable(client->timeout_t1);
601 (void) event_source_disable(client->timeout_t2);
213e759a 602
65b85f23 603 client_set_state(client, DHCP6_STATE_STOPPED);
139b011a
PF
604}
605
1929c1fc
YW
606static void client_stop(sd_dhcp6_client *client, int error) {
607 DHCP6_CLIENT_DONT_DESTROY(client);
608
609 assert(client);
610
611 client_notify(client, error);
612
613 client_cleanup(client);
614}
615
5e4d135c
YW
616static int client_append_common_options_in_managed_mode(
617 sd_dhcp6_client *client,
9d2d346a
YW
618 uint8_t **buf,
619 size_t *offset,
5e4d135c 620 const DHCP6IA *ia_na,
877bfc78 621 const DHCP6IA *ia_pd) {
5e4d135c
YW
622
623 int r;
624
625 assert(client);
626 assert(IN_SET(client->state,
627 DHCP6_STATE_SOLICITATION,
628 DHCP6_STATE_REQUEST,
629 DHCP6_STATE_RENEW,
b895aa5f 630 DHCP6_STATE_REBIND,
631 DHCP6_STATE_STOPPING));
9d2d346a
YW
632 assert(buf);
633 assert(*buf);
634 assert(offset);
5e4d135c
YW
635
636 if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_NA) && ia_na) {
9d2d346a 637 r = dhcp6_option_append_ia(buf, offset, ia_na);
5e4d135c
YW
638 if (r < 0)
639 return r;
640 }
641
642 if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_PD) && ia_pd) {
9d2d346a 643 r = dhcp6_option_append_ia(buf, offset, ia_pd);
5e4d135c
YW
644 if (r < 0)
645 return r;
646 }
647
b895aa5f 648 if (client->state != DHCP6_STATE_STOPPING) {
649 r = dhcp6_option_append_fqdn(buf, offset, client->fqdn);
650 if (r < 0)
651 return r;
652 }
5e4d135c 653
9d2d346a 654 r = dhcp6_option_append_user_class(buf, offset, client->user_class);
4ec5b5c7
YW
655 if (r < 0)
656 return r;
5e4d135c 657
9d2d346a 658 r = dhcp6_option_append_vendor_class(buf, offset, client->vendor_class);
4ec5b5c7
YW
659 if (r < 0)
660 return r;
5e4d135c 661
9d2d346a 662 r = dhcp6_option_append_vendor_option(buf, offset, client->vendor_options);
4ec5b5c7
YW
663 if (r < 0)
664 return r;
5e4d135c
YW
665
666 return 0;
667}
668
669static DHCP6MessageType client_message_type_from_state(sd_dhcp6_client *client) {
670 assert(client);
671
672 switch (client->state) {
673 case DHCP6_STATE_INFORMATION_REQUEST:
674 return DHCP6_MESSAGE_INFORMATION_REQUEST;
675 case DHCP6_STATE_SOLICITATION:
676 return DHCP6_MESSAGE_SOLICIT;
677 case DHCP6_STATE_REQUEST:
678 return DHCP6_MESSAGE_REQUEST;
679 case DHCP6_STATE_RENEW:
680 return DHCP6_MESSAGE_RENEW;
681 case DHCP6_STATE_REBIND:
682 return DHCP6_MESSAGE_REBIND;
b895aa5f 683 case DHCP6_STATE_STOPPING:
684 return DHCP6_MESSAGE_RELEASE;
5e4d135c 685 default:
6f4490bb 686 assert_not_reached();
5e4d135c
YW
687 }
688}
689
9d2d346a 690static int client_append_oro(sd_dhcp6_client *client, uint8_t **buf, size_t *offset) {
04542238 691 _cleanup_free_ be16_t *p = NULL;
822883b3
YW
692 be16_t *req_opts;
693 size_t n;
694
695 assert(client);
9d2d346a
YW
696 assert(buf);
697 assert(*buf);
698 assert(offset);
822883b3
YW
699
700 switch (client->state) {
701 case DHCP6_STATE_INFORMATION_REQUEST:
702 n = client->n_req_opts;
04542238
YW
703 p = new(be16_t, n + 2);
704 if (!p)
822883b3
YW
705 return -ENOMEM;
706
04542238
YW
707 memcpy_safe(p, client->req_opts, n * sizeof(be16_t));
708 p[n++] = htobe16(SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME); /* RFC 8415 section 21.23 */
709 p[n++] = htobe16(SD_DHCP6_OPTION_INF_MAX_RT); /* RFC 8415 section 21.25 */
822883b3 710
04542238
YW
711 typesafe_qsort(p, n, be16_compare_func);
712 req_opts = p;
822883b3
YW
713 break;
714
715 case DHCP6_STATE_SOLICITATION:
716 n = client->n_req_opts;
04542238
YW
717 p = new(be16_t, n + 1);
718 if (!p)
822883b3
YW
719 return -ENOMEM;
720
04542238
YW
721 memcpy_safe(p, client->req_opts, n * sizeof(be16_t));
722 p[n++] = htobe16(SD_DHCP6_OPTION_SOL_MAX_RT); /* RFC 8415 section 21.24 */
822883b3 723
04542238
YW
724 typesafe_qsort(p, n, be16_compare_func);
725 req_opts = p;
822883b3
YW
726 break;
727
b895aa5f 728 case DHCP6_STATE_STOPPING:
729 return 0;
730
822883b3
YW
731 default:
732 n = client->n_req_opts;
733 req_opts = client->req_opts;
734 }
735
4c275f36
YW
736 if (n == 0)
737 return 0;
738
9d2d346a 739 return dhcp6_option_append(buf, offset, SD_DHCP6_OPTION_ORO, n * sizeof(be16_t), req_opts);
822883b3
YW
740}
741
b895aa5f 742static int client_append_mudurl(sd_dhcp6_client *client, uint8_t **buf, size_t *offset) {
743 assert(client);
744 assert(buf);
745 assert(*buf);
746 assert(offset);
747
748 if (!client->mudurl)
749 return 0;
750
751 if (client->state == DHCP6_STATE_STOPPING)
752 return 0;
753
754 return dhcp6_option_append(buf, offset, SD_DHCP6_OPTION_MUD_URL_V6,
755 strlen(client->mudurl), client->mudurl);
756}
757
7b53d3ea 758int dhcp6_client_send_message(sd_dhcp6_client *client) {
9d2d346a 759 _cleanup_free_ uint8_t *buf = NULL;
a9aff361
PF
760 struct in6_addr all_servers =
761 IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
e7d5fe17 762 struct sd_dhcp6_option *j;
ec7baf99 763 usec_t elapsed_usec, time_now;
346e13a2 764 be16_t elapsed_time;
9d2d346a
YW
765 DHCP6Message *message;
766 size_t offset;
5e4d135c 767 int r;
a9aff361 768
a1140666 769 assert(client);
ec7baf99
YW
770 assert(client->event);
771
ba4e0427 772 r = sd_event_now(client->event, CLOCK_BOOTTIME, &time_now);
ec7baf99
YW
773 if (r < 0)
774 return r;
a1140666 775
9d2d346a 776 if (!GREEDY_REALLOC0(buf, offsetof(DHCP6Message, options)))
a9aff361
PF
777 return -ENOMEM;
778
9d2d346a 779 message = (DHCP6Message*) buf;
a9aff361 780 message->transaction_id = client->transaction_id;
6f4490bb 781 message->type = client_message_type_from_state(client);
9d2d346a 782 offset = offsetof(DHCP6Message, options);
de8d6e55 783
5e4d135c
YW
784 switch (client->state) {
785 case DHCP6_STATE_INFORMATION_REQUEST:
bbfa43ca
PF
786 break;
787
a9aff361 788 case DHCP6_STATE_SOLICITATION:
4397967f 789 if (client->rapid_commit) {
9d2d346a 790 r = dhcp6_option_append(&buf, &offset, SD_DHCP6_OPTION_RAPID_COMMIT, 0, NULL);
4397967f
YW
791 if (r < 0)
792 return r;
793 }
ed6ee219 794
9d2d346a 795 r = client_append_common_options_in_managed_mode(client, &buf, &offset,
877bfc78 796 &client->ia_na, &client->ia_pd);
5e4d135c
YW
797 if (r < 0)
798 return r;
7246333c
PF
799 break;
800
801 case DHCP6_STATE_REQUEST:
3dc34fcc 802 case DHCP6_STATE_RENEW:
b895aa5f 803 case DHCP6_STATE_STOPPING:
9d2d346a 804 r = dhcp6_option_append(&buf, &offset, SD_DHCP6_OPTION_SERVERID,
7246333c
PF
805 client->lease->serverid_len,
806 client->lease->serverid);
807 if (r < 0)
808 return r;
809
5e4d135c 810 _fallthrough_;
3dc34fcc 811 case DHCP6_STATE_REBIND:
3dc34fcc 812
5e4d135c 813 assert(client->lease);
7c3de8f8 814
9d2d346a 815 r = client_append_common_options_in_managed_mode(client, &buf, &offset,
877bfc78 816 client->lease->ia_na, client->lease->ia_pd);
5e4d135c
YW
817 if (r < 0)
818 return r;
3dc34fcc
PF
819 break;
820
a34b57c0 821 case DHCP6_STATE_BOUND:
b895aa5f 822 case DHCP6_STATE_STOPPED:
dd5e9378
YW
823 default:
824 assert_not_reached();
a9aff361
PF
825 }
826
b895aa5f 827 r = client_append_mudurl(client, &buf, &offset);
828 if (r < 0)
829 return r;
5e4d135c 830
9d2d346a 831 r = client_append_oro(client, &buf, &offset);
da6fe470
PF
832 if (r < 0)
833 return r;
834
4124b03b 835 assert(client->duid_len > 0);
9d2d346a 836 r = dhcp6_option_append(&buf, &offset, SD_DHCP6_OPTION_CLIENTID,
66eac120 837 client->duid_len, &client->duid);
7246333c
PF
838 if (r < 0)
839 return r;
840
bbe3f62a 841 ORDERED_HASHMAP_FOREACH(j, client->extra_options) {
9d2d346a 842 r = dhcp6_option_append(&buf, &offset, j->option, j->length, j->data);
bbe3f62a
YW
843 if (r < 0)
844 return r;
845 }
846
aa5a0f95
YW
847 /* RFC 8415 Section 21.9.
848 * A client MUST include an Elapsed Time option in messages to indicate how long the client has
849 * been trying to complete a DHCP message exchange. */
850 elapsed_usec = MIN(usec_sub_unsigned(time_now, client->transaction_start) / USEC_PER_MSEC / 10, (usec_t) UINT16_MAX);
851 elapsed_time = htobe16(elapsed_usec);
9d2d346a 852 r = dhcp6_option_append(&buf, &offset, SD_DHCP6_OPTION_ELAPSED_TIME, sizeof(elapsed_time), &elapsed_time);
346e13a2
PF
853 if (r < 0)
854 return r;
855
9d2d346a 856 r = dhcp6_network_send_udp_socket(client->fd, &all_servers, buf, offset);
a9aff361
PF
857 if (r < 0)
858 return r;
859
860 log_dhcp6_client(client, "Sent %s",
9d2d346a 861 dhcp6_message_type_to_string(client_message_type_from_state(client)));
a9aff361
PF
862 return 0;
863}
864
cc518482
YW
865static usec_t client_timeout_compute_random(usec_t val) {
866 return usec_sub_unsigned(val, random_u64_range(val / 10));
867}
868
c50c9e50
YW
869static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userdata) {
870 sd_dhcp6_client *client = ASSERT_PTR(userdata);
871 usec_t init_retransmit_time, max_retransmit_time;
872 int r;
873
c50c9e50
YW
874 assert(client->event);
875
876 switch (client->state) {
877 case DHCP6_STATE_INFORMATION_REQUEST:
878 init_retransmit_time = DHCP6_INF_TIMEOUT;
879 max_retransmit_time = DHCP6_INF_MAX_RT;
880 break;
881
882 case DHCP6_STATE_SOLICITATION:
883
884 if (client->retransmit_count > 0 && client->lease) {
e5d69be2 885 (void) client_start_transaction(client, DHCP6_STATE_REQUEST);
c50c9e50
YW
886 return 0;
887 }
888
889 init_retransmit_time = DHCP6_SOL_TIMEOUT;
890 max_retransmit_time = DHCP6_SOL_MAX_RT;
891 break;
892
893 case DHCP6_STATE_REQUEST:
894
895 if (client->retransmit_count >= DHCP6_REQ_MAX_RC) {
896 client_stop(client, SD_DHCP6_CLIENT_EVENT_RETRANS_MAX);
897 return 0;
898 }
899
900 init_retransmit_time = DHCP6_REQ_TIMEOUT;
901 max_retransmit_time = DHCP6_REQ_MAX_RT;
902 break;
903
904 case DHCP6_STATE_RENEW:
905 init_retransmit_time = DHCP6_REN_TIMEOUT;
906 max_retransmit_time = DHCP6_REN_MAX_RT;
907
908 /* RFC 3315, section 18.1.3. says max retransmit duration will
909 be the remaining time until T2. Instead of setting MRD,
910 wait for T2 to trigger with the same end result */
911 break;
912
913 case DHCP6_STATE_REBIND:
914 init_retransmit_time = DHCP6_REB_TIMEOUT;
915 max_retransmit_time = DHCP6_REB_MAX_RT;
916
917 /* Also, instead of setting MRD, the expire timer is already set in client_enter_bound_state(). */
918 break;
919
920 case DHCP6_STATE_STOPPED:
b895aa5f 921 case DHCP6_STATE_STOPPING:
c50c9e50
YW
922 case DHCP6_STATE_BOUND:
923 default:
924 assert_not_reached();
925 }
926
7b53d3ea 927 r = dhcp6_client_send_message(client);
c50c9e50
YW
928 if (r >= 0)
929 client->retransmit_count++;
930
931 if (client->retransmit_time == 0) {
932 client->retransmit_time = client_timeout_compute_random(init_retransmit_time);
933
934 if (client->state == DHCP6_STATE_SOLICITATION)
935 client->retransmit_time += init_retransmit_time / 10;
936
937 } else if (client->retransmit_time > max_retransmit_time / 2)
938 client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
939 else
940 client->retransmit_time += client_timeout_compute_random(client->retransmit_time);
941
942 log_dhcp6_client(client, "Next retransmission in %s",
943 FORMAT_TIMESPAN(client->retransmit_time, USEC_PER_SEC));
944
945 r = event_reset_time_relative(client->event, &client->timeout_resend,
ba4e0427 946 CLOCK_BOOTTIME,
c50c9e50
YW
947 client->retransmit_time, 10 * USEC_PER_MSEC,
948 client_timeout_resend, client,
949 client->event_priority, "dhcp6-resend-timer", true);
950 if (r < 0)
951 client_stop(client, r);
952
953 return 0;
954}
955
e5d69be2 956static int client_start_transaction(sd_dhcp6_client *client, DHCP6State state) {
c50c9e50
YW
957 int r;
958
6f4490bb
YW
959 assert(client);
960 assert(client->event);
c50c9e50
YW
961
962 switch (state) {
963 case DHCP6_STATE_INFORMATION_REQUEST:
964 case DHCP6_STATE_SOLICITATION:
965 assert(client->state == DHCP6_STATE_STOPPED);
966 break;
967 case DHCP6_STATE_REQUEST:
968 assert(client->state == DHCP6_STATE_SOLICITATION);
969 break;
970 case DHCP6_STATE_RENEW:
971 assert(client->state == DHCP6_STATE_BOUND);
972 break;
973 case DHCP6_STATE_REBIND:
974 assert(IN_SET(client->state, DHCP6_STATE_BOUND, DHCP6_STATE_RENEW));
975 break;
976 case DHCP6_STATE_STOPPED:
b895aa5f 977 case DHCP6_STATE_STOPPING:
c50c9e50
YW
978 case DHCP6_STATE_BOUND:
979 default:
980 assert_not_reached();
981 }
982
65b85f23
YW
983 client_set_state(client, state);
984
c50c9e50
YW
985 client->retransmit_time = 0;
986 client->retransmit_count = 0;
987 client->transaction_id = random_u32() & htobe32(0x00ffffff);
988
ba4e0427 989 r = sd_event_now(client->event, CLOCK_BOOTTIME, &client->transaction_start);
c50c9e50
YW
990 if (r < 0)
991 goto error;
992
993 r = event_reset_time(client->event, &client->timeout_resend,
ba4e0427 994 CLOCK_BOOTTIME,
c50c9e50
YW
995 0, 0,
996 client_timeout_resend, client,
997 client->event_priority, "dhcp6-resend-timeout", true);
998 if (r < 0)
999 goto error;
1000
1001 r = sd_event_source_set_enabled(client->receive_message, SD_EVENT_ON);
1002 if (r < 0)
1003 goto error;
1004
1005 return 0;
1006
1007error:
1008 client_stop(client, r);
1009 return r;
1010}
1011
3bb18e70 1012static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userdata) {
6f4490bb 1013 sd_dhcp6_client *client = ASSERT_PTR(userdata);
3bb18e70
YW
1014 DHCP6_CLIENT_DONT_DESTROY(client);
1015 DHCP6State state;
1016
3bb18e70
YW
1017 (void) event_source_disable(client->timeout_expire);
1018 (void) event_source_disable(client->timeout_t2);
1019 (void) event_source_disable(client->timeout_t1);
1020
1021 state = client->state;
1022
1023 client_stop(client, SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE);
1024
1025 /* RFC 3315, section 18.1.4., says that "...the client may choose to
1026 use a Solicit message to locate a new DHCP server..." */
1027 if (state == DHCP6_STATE_REBIND)
e5d69be2 1028 (void) client_start_transaction(client, DHCP6_STATE_SOLICITATION);
3bb18e70
YW
1029
1030 return 0;
1031}
1032
4b558378 1033static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) {
6f4490bb 1034 sd_dhcp6_client *client = ASSERT_PTR(userdata);
a34b57c0 1035
c9393e8c 1036 (void) event_source_disable(client->timeout_t2);
220a88ca 1037 (void) event_source_disable(client->timeout_t1);
a34b57c0
PF
1038
1039 log_dhcp6_client(client, "Timeout T2");
1040
e5d69be2 1041 (void) client_start_transaction(client, DHCP6_STATE_REBIND);
3dc34fcc 1042
a34b57c0
PF
1043 return 0;
1044}
1045
4b558378 1046static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) {
6f4490bb 1047 sd_dhcp6_client *client = ASSERT_PTR(userdata);
a34b57c0 1048
c9393e8c 1049 (void) event_source_disable(client->timeout_t1);
a34b57c0
PF
1050
1051 log_dhcp6_client(client, "Timeout T1");
1052
e5d69be2 1053 (void) client_start_transaction(client, DHCP6_STATE_RENEW);
3dc34fcc 1054
a34b57c0
PF
1055 return 0;
1056}
1057
02354ee7 1058static int client_enter_bound_state(sd_dhcp6_client *client) {
3bb18e70 1059 usec_t lifetime_t1, lifetime_t2, lifetime_valid;
02354ee7
YW
1060 int r;
1061
1062 assert(client);
1063 assert(client->lease);
1064 assert(IN_SET(client->state,
1065 DHCP6_STATE_SOLICITATION,
1066 DHCP6_STATE_REQUEST,
1067 DHCP6_STATE_RENEW,
1068 DHCP6_STATE_REBIND));
1069
6f8ff342 1070 (void) event_source_disable(client->receive_message);
02354ee7
YW
1071 (void) event_source_disable(client->timeout_resend);
1072
3bb18e70 1073 r = dhcp6_lease_get_lifetime(client->lease, &lifetime_t1, &lifetime_t2, &lifetime_valid);
02354ee7
YW
1074 if (r < 0)
1075 goto error;
1076
cdf3d8c5
YW
1077 lifetime_t2 = client_timeout_compute_random(lifetime_t2);
1078 lifetime_t1 = client_timeout_compute_random(MIN(lifetime_t1, lifetime_t2));
02354ee7 1079
cdf3d8c5
YW
1080 if (lifetime_t1 == USEC_INFINITY) {
1081 log_dhcp6_client(client, "Infinite T1");
1082 event_source_disable(client->timeout_t1);
1083 } else {
1084 log_dhcp6_client(client, "T1 expires in %s", FORMAT_TIMESPAN(lifetime_t1, USEC_PER_SEC));
1085 r = event_reset_time_relative(client->event, &client->timeout_t1,
ba4e0427 1086 CLOCK_BOOTTIME,
cdf3d8c5
YW
1087 lifetime_t1, 10 * USEC_PER_SEC,
1088 client_timeout_t1, client,
1089 client->event_priority, "dhcp6-t1-timeout", true);
1090 if (r < 0)
1091 goto error;
1092 }
02354ee7 1093
cdf3d8c5
YW
1094 if (lifetime_t2 == USEC_INFINITY) {
1095 log_dhcp6_client(client, "Infinite T2");
1096 event_source_disable(client->timeout_t2);
1097 } else {
1098 log_dhcp6_client(client, "T2 expires in %s", FORMAT_TIMESPAN(lifetime_t2, USEC_PER_SEC));
1099 r = event_reset_time_relative(client->event, &client->timeout_t2,
ba4e0427 1100 CLOCK_BOOTTIME,
cdf3d8c5
YW
1101 lifetime_t2, 10 * USEC_PER_SEC,
1102 client_timeout_t2, client,
1103 client->event_priority, "dhcp6-t2-timeout", true);
1104 if (r < 0)
1105 goto error;
1106 }
02354ee7 1107
3bb18e70
YW
1108 if (lifetime_valid == USEC_INFINITY) {
1109 log_dhcp6_client(client, "Infinite valid lifetime");
1110 event_source_disable(client->timeout_expire);
1111 } else {
1112 log_dhcp6_client(client, "Valid lifetime expires in %s", FORMAT_TIMESPAN(lifetime_valid, USEC_PER_SEC));
1113
1114 r = event_reset_time_relative(client->event, &client->timeout_expire,
ba4e0427 1115 CLOCK_BOOTTIME,
3bb18e70
YW
1116 lifetime_valid, USEC_PER_SEC,
1117 client_timeout_expire, client,
1118 client->event_priority, "dhcp6-lease-expire", true);
1119 if (r < 0)
1120 goto error;
1121 }
1122
65b85f23 1123 client_set_state(client, DHCP6_STATE_BOUND);
c41bdb17 1124 client_notify(client, SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE);
02354ee7
YW
1125 return 0;
1126
1127error:
1128 client_stop(client, r);
1129 return r;
1130}
1131
07a3b340
YW
1132static int log_invalid_message_type(sd_dhcp6_client *client, const DHCP6Message *message) {
1133 const char *type_str;
1134
1135 assert(client);
1136 assert(message);
1137
1138 type_str = dhcp6_message_type_to_string(message->type);
1139 if (type_str)
1140 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
1141 "Received unexpected %s message, ignoring.", type_str);
1142 else
1143 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
1144 "Received unsupported message type %u, ignoring.", message->type);
1145}
1146
1147static int client_process_information(
ef4edc15 1148 sd_dhcp6_client *client,
65ece4c8 1149 DHCP6Message *message,
ef4edc15 1150 size_t len,
65ece4c8 1151 const triple_timestamp *timestamp,
ef4edc15
YW
1152 const struct in6_addr *server_address) {
1153
4afd3348 1154 _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
a1140666
LP
1155 int r;
1156
6f4490bb
YW
1157 assert(client);
1158 assert(message);
1159
65ece4c8 1160 if (message->type != DHCP6_MESSAGE_REPLY)
07a3b340 1161 return log_invalid_message_type(client, message);
a34b57c0 1162
65ece4c8 1163 r = dhcp6_lease_new_from_message(client, message, len, timestamp, server_address, &lease);
a34b57c0 1164 if (r < 0)
07a3b340 1165 return log_dhcp6_client_errno(client, r, "Failed to process received reply message, ignoring: %m");
a34b57c0 1166
cfcc85bb
YW
1167 log_dhcp6_client(client, "Processed %s message", dhcp6_message_type_to_string(message->type));
1168
07a3b340
YW
1169 sd_dhcp6_lease_unref(client->lease);
1170 client->lease = TAKE_PTR(lease);
ed6ee219 1171
6f8ff342
YW
1172 /* Do not call client_stop() here, as it frees the acquired lease. */
1173 (void) event_source_disable(client->receive_message);
c2c878d8 1174 (void) event_source_disable(client->timeout_resend);
65b85f23 1175 client_set_state(client, DHCP6_STATE_STOPPED);
c2c878d8 1176
07a3b340 1177 client_notify(client, SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST);
c2c878d8 1178 return 0;
07a3b340
YW
1179}
1180
1181static int client_process_reply(
1182 sd_dhcp6_client *client,
1183 DHCP6Message *message,
1184 size_t len,
1185 const triple_timestamp *timestamp,
1186 const struct in6_addr *server_address) {
1187
1188 _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
1189 int r;
1190
1191 assert(client);
1192 assert(message);
1193
1194 if (message->type != DHCP6_MESSAGE_REPLY)
1195 return log_invalid_message_type(client, message);
1196
1197 r = dhcp6_lease_new_from_message(client, message, len, timestamp, server_address, &lease);
1929c1fc
YW
1198 if (r == -EADDRNOTAVAIL) {
1199
1200 /* If NoBinding status code is received, we cannot request the address anymore.
1201 * Let's restart transaction from the beginning. */
1202
1203 if (client->state == DHCP6_STATE_REQUEST)
1204 /* The lease is not acquired yet, hence it is not necessary to notify the restart. */
1205 client_cleanup(client);
1206 else
1207 /* We need to notify the previous lease was expired. */
1208 client_stop(client, SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE);
1209
1210 return client_start_transaction(client, DHCP6_STATE_SOLICITATION);
1211 }
07a3b340
YW
1212 if (r < 0)
1213 return log_dhcp6_client_errno(client, r, "Failed to process received reply message, ignoring: %m");
1214
cfcc85bb
YW
1215 log_dhcp6_client(client, "Processed %s message", dhcp6_message_type_to_string(message->type));
1216
431a4bc8
YW
1217 sd_dhcp6_lease_unref(client->lease);
1218 client->lease = TAKE_PTR(lease);
a34b57c0 1219
c41bdb17 1220 return client_enter_bound_state(client);
a34b57c0
PF
1221}
1222
07a3b340 1223static int client_process_advertise_or_rapid_commit_reply(
ef4edc15 1224 sd_dhcp6_client *client,
65ece4c8 1225 DHCP6Message *message,
ef4edc15 1226 size_t len,
65ece4c8 1227 const triple_timestamp *timestamp,
ef4edc15
YW
1228 const struct in6_addr *server_address) {
1229
4afd3348 1230 _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
07a3b340 1231 uint8_t pref_advertise, pref_lease = 0;
a1140666 1232 int r;
631bbe71 1233
653ddc1d 1234 assert(client);
65ece4c8 1235 assert(message);
653ddc1d 1236
07a3b340
YW
1237 if (!IN_SET(message->type, DHCP6_MESSAGE_ADVERTISE, DHCP6_MESSAGE_REPLY))
1238 return log_invalid_message_type(client, message);
631bbe71 1239
65ece4c8 1240 r = dhcp6_lease_new_from_message(client, message, len, timestamp, server_address, &lease);
631bbe71 1241 if (r < 0)
07a3b340
YW
1242 return log_dhcp6_client_errno(client, r, "Failed to process received %s message, ignoring: %m",
1243 dhcp6_message_type_to_string(message->type));
1244
1245 if (message->type == DHCP6_MESSAGE_REPLY) {
1246 bool rapid_commit;
1247
4397967f
YW
1248 if (!client->rapid_commit)
1249 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
1250 "Received unexpected reply message, even we sent a solicit message without the rapid commit option, ignoring.");
1251
07a3b340
YW
1252 r = dhcp6_lease_get_rapid_commit(lease, &rapid_commit);
1253 if (r < 0)
1254 return r;
1255
1256 if (!rapid_commit)
1257 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
1258 "Received reply message without rapid commit flag, ignoring.");
1259
cfcc85bb
YW
1260 log_dhcp6_client(client, "Processed %s message", dhcp6_message_type_to_string(message->type));
1261
07a3b340
YW
1262 sd_dhcp6_lease_unref(client->lease);
1263 client->lease = TAKE_PTR(lease);
1264
c41bdb17 1265 return client_enter_bound_state(client);
07a3b340 1266 }
631bbe71
PF
1267
1268 r = dhcp6_lease_get_preference(lease, &pref_advertise);
1269 if (r < 0)
1270 return r;
1271
049fddfa
YW
1272 if (client->lease) {
1273 r = dhcp6_lease_get_preference(client->lease, &pref_lease);
1274 if (r < 0)
1275 return r;
1276 }
bbfa43ca 1277
cfcc85bb
YW
1278 log_dhcp6_client(client, "Processed %s message", dhcp6_message_type_to_string(message->type));
1279
049fddfa 1280 if (!client->lease || pref_advertise > pref_lease) {
07a3b340 1281 /* If this is the first advertise message or has higher preference, then save the lease. */
431a4bc8
YW
1282 sd_dhcp6_lease_unref(client->lease);
1283 client->lease = TAKE_PTR(lease);
631bbe71
PF
1284 }
1285
cfcc85bb 1286 if (pref_advertise == 255 || client->retransmit_count > 1)
e5d69be2 1287 (void) client_start_transaction(client, DHCP6_STATE_REQUEST);
7246333c 1288
07a3b340 1289 return 0;
631bbe71
PF
1290}
1291
004845d1
LP
1292static int client_receive_message(
1293 sd_event_source *s,
1294 int fd, uint32_t
1295 revents,
1296 void *userdata) {
1297
6f4490bb 1298 sd_dhcp6_client *client = ASSERT_PTR(userdata);
3f0c075f 1299 DHCP6_CLIENT_DONT_DESTROY(client);
653ddc1d
YW
1300 /* This needs to be initialized with zero. See #20741. */
1301 CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL) control = {};
1302 struct iovec iov;
ef4edc15 1303 union sockaddr_union sa = {};
653ddc1d 1304 struct msghdr msg = {
ef4edc15
YW
1305 .msg_name = &sa.sa,
1306 .msg_namelen = sizeof(sa),
653ddc1d
YW
1307 .msg_iov = &iov,
1308 .msg_iovlen = 1,
1309 .msg_control = &control,
1310 .msg_controllen = sizeof(control),
1311 };
653ddc1d 1312 triple_timestamp t = {};
0d43d2fc 1313 _cleanup_free_ DHCP6Message *message = NULL;
ef4edc15 1314 struct in6_addr *server_address = NULL;
4edc2c9b 1315 ssize_t buflen, len;
631bbe71 1316
4edc2c9b 1317 buflen = next_datagram_size_fd(fd);
1f2db2e3
ZJS
1318 if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen))
1319 return 0;
ab8a8a4e 1320 if (buflen < 0) {
ab8a8a4e 1321 log_dhcp6_client_errno(client, buflen, "Failed to determine datagram size to read, ignoring: %m");
22a3fd2d 1322 return 0;
ab8a8a4e 1323 }
631bbe71 1324
0d43d2fc 1325 message = malloc(buflen);
631bbe71
PF
1326 if (!message)
1327 return -ENOMEM;
1328
653ddc1d
YW
1329 iov = IOVEC_MAKE(message, buflen);
1330
1331 len = recvmsg_safe(fd, &msg, MSG_DONTWAIT);
1f2db2e3
ZJS
1332 if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len))
1333 return 0;
0d43d2fc 1334 if (len < 0) {
ab8a8a4e
YW
1335 log_dhcp6_client_errno(client, len, "Could not receive message from UDP socket, ignoring: %m");
1336 return 0;
004845d1
LP
1337 }
1338 if ((size_t) len < sizeof(DHCP6Message)) {
1339 log_dhcp6_client(client, "Too small to be DHCP6 message: ignoring");
631bbe71 1340 return 0;
004845d1 1341 }
631bbe71 1342
ef4edc15
YW
1343 /* msg_namelen == 0 happens when running the test-suite over a socketpair */
1344 if (msg.msg_namelen > 0) {
1345 if (msg.msg_namelen != sizeof(struct sockaddr_in6) || sa.in6.sin6_family != AF_INET6) {
1346 log_dhcp6_client(client, "Received message from invalid source, ignoring.");
1347 return 0;
1348 }
1349
1350 server_address = &sa.in6.sin6_addr;
1351 }
1352
789f5c6f
YW
1353 struct timeval *tv = CMSG_FIND_AND_COPY_DATA(&msg, SOL_SOCKET, SCM_TIMESTAMP, struct timeval);
1354 if (tv)
1355 triple_timestamp_from_realtime(&t, timeval_load(tv));
653ddc1d 1356
d7799877 1357 if (client->transaction_id != (message->transaction_id & htobe32(0x00ffffff)))
631bbe71
PF
1358 return 0;
1359
1360 switch (client->state) {
bbfa43ca 1361 case DHCP6_STATE_INFORMATION_REQUEST:
07a3b340 1362 if (client_process_information(client, message, len, &t, server_address) < 0)
bbfa43ca 1363 return 0;
bbfa43ca
PF
1364 break;
1365
631bbe71 1366 case DHCP6_STATE_SOLICITATION:
07a3b340 1367 if (client_process_advertise_or_rapid_commit_reply(client, message, len, &t, server_address) < 0)
d7799877 1368 return 0;
07a3b340 1369 break;
631bbe71 1370
7246333c 1371 case DHCP6_STATE_REQUEST:
3dc34fcc
PF
1372 case DHCP6_STATE_RENEW:
1373 case DHCP6_STATE_REBIND:
07a3b340 1374 if (client_process_reply(client, message, len, &t, server_address) < 0)
a34b57c0 1375 return 0;
a34b57c0
PF
1376 break;
1377
1378 case DHCP6_STATE_BOUND:
631bbe71 1379 case DHCP6_STATE_STOPPED:
b895aa5f 1380 case DHCP6_STATE_STOPPING:
dd5e9378
YW
1381 default:
1382 assert_not_reached();
631bbe71
PF
1383 }
1384
a9aff361
PF
1385 return 0;
1386}
1387
b895aa5f 1388static int client_send_release(sd_dhcp6_client *client) {
1389 sd_dhcp6_lease *lease;
1390
1391 assert(client);
1392
1393 if (!client->send_release)
1394 return 0;
1395
1396 if (sd_dhcp6_client_get_lease(client, &lease) < 0)
1397 return 0;
1398
1399 if (!lease->ia_na && !lease->ia_pd)
1400 return 0;
1401
1402 client_set_state(client, DHCP6_STATE_STOPPING);
1403 return dhcp6_client_send_message(client);
1404}
1405
0ae0e5cd 1406int sd_dhcp6_client_stop(sd_dhcp6_client *client) {
b895aa5f 1407 int r;
1408
c8bae363
YW
1409 if (!client)
1410 return 0;
f667c150 1411
b895aa5f 1412 /* Intentionally ignoring failure to send DHCP6 release. The DHCPv6 client
d09df6b9 1413 * engine is about to release its UDP socket unconditionally. */
b895aa5f 1414 r = client_send_release(client);
1415 if (r < 0)
1416 log_dhcp6_client_errno(client, r,
1417 "Failed to send DHCP6 release message, ignoring: %m");
1418
10c9ce61 1419 client_stop(client, SD_DHCP6_CLIENT_EVENT_STOP);
139b011a 1420
8ef959cd 1421 client->receive_message = sd_event_source_unref(client->receive_message);
7ac6c26a
PF
1422 client->fd = safe_close(client->fd);
1423
139b011a
PF
1424 return 0;
1425}
1426
f667c150
TG
1427int sd_dhcp6_client_is_running(sd_dhcp6_client *client) {
1428 assert_return(client, -EINVAL);
1429
1430 return client->state != DHCP6_STATE_STOPPED;
1431}
1432
0ae0e5cd 1433int sd_dhcp6_client_start(sd_dhcp6_client *client) {
dd5e9378 1434 DHCP6State state = DHCP6_STATE_SOLICITATION;
6d95e7d9 1435 int r;
139b011a
PF
1436
1437 assert_return(client, -EINVAL);
1438 assert_return(client->event, -EINVAL);
2f8e7633 1439 assert_return(client->ifindex > 0, -EINVAL);
94876904 1440 assert_return(in6_addr_is_link_local(&client->local_address) > 0, -EINVAL);
6f4490bb
YW
1441 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
1442 assert_return(client->information_request || client->request_ia != 0, -EINVAL);
f311a62b 1443
af2b4841
YW
1444 /* Even if the client is in the STOPPED state, the lease acquired in the previous information
1445 * request may be stored. */
1446 client->lease = sd_dhcp6_lease_unref(client->lease);
f12abb48 1447
bbfa43ca
PF
1448 r = client_ensure_iaid(client);
1449 if (r < 0)
1450 return r;
1451
cc22955c
TH
1452 r = client_ensure_duid(client);
1453 if (r < 0)
1454 return r;
1455
10a0f27b
PF
1456 if (client->fd < 0) {
1457 r = dhcp6_network_bind_udp_socket(client->ifindex, &client->local_address);
84dbb3fd 1458 if (r < 0)
10a0f27b 1459 return log_dhcp6_client_errno(client, r,
84dbb3fd
ZJS
1460 "Failed to bind to UDP socket at address %s: %m",
1461 IN6_ADDR_TO_STRING(&client->local_address));
bd9a7221 1462
10a0f27b 1463 client->fd = r;
bd9a7221 1464 }
bbfa43ca 1465
8ef959cd
YW
1466 if (!client->receive_message) {
1467 _cleanup_(sd_event_source_disable_unrefp) sd_event_source *s = NULL;
1468
1469 r = sd_event_add_io(client->event, &s, client->fd, EPOLLIN, client_receive_message, client);
1470 if (r < 0)
1471 return r;
1472
1473 r = sd_event_source_set_priority(s, client->event_priority);
1474 if (r < 0)
1475 return r;
1476
1477 r = sd_event_source_set_description(s, "dhcp6-receive-message");
1478 if (r < 0)
1479 return r;
1480
1481 client->receive_message = TAKE_PTR(s);
1482 }
1483
fcb51238
YW
1484 if (client->information_request) {
1485 usec_t t = now(CLOCK_MONOTONIC);
1486
1487 if (t < usec_add(client->information_request_time_usec, client->information_refresh_time_usec))
1488 return 0;
1489
1490 client->information_request_time_usec = t;
bbfa43ca 1491 state = DHCP6_STATE_INFORMATION_REQUEST;
fcb51238 1492 }
bbfa43ca 1493
65b85f23 1494 log_dhcp6_client(client, "Starting in %s mode",
0bcc6557 1495 client->information_request ? "Information request" : "Solicit");
bbfa43ca 1496
e5d69be2 1497 return client_start_transaction(client, state);
139b011a
PF
1498}
1499
32d20645 1500int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, int64_t priority) {
139b011a
PF
1501 int r;
1502
1503 assert_return(client, -EINVAL);
1504 assert_return(!client->event, -EBUSY);
6f4490bb 1505 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
139b011a
PF
1506
1507 if (event)
1508 client->event = sd_event_ref(event);
1509 else {
1510 r = sd_event_default(&client->event);
1511 if (r < 0)
1512 return 0;
1513 }
1514
1515 client->event_priority = priority;
1516
1517 return 0;
1518}
1519
1520int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) {
1521 assert_return(client, -EINVAL);
6f4490bb 1522 assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
139b011a
PF
1523
1524 client->event = sd_event_unref(client->event);
1525
1526 return 0;
1527}
1528
1529sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
a1140666 1530 assert_return(client, NULL);
139b011a
PF
1531
1532 return client->event;
1533}
1534
793178b9
YW
1535int sd_dhcp6_client_attach_device(sd_dhcp6_client *client, sd_device *dev) {
1536 assert_return(client, -EINVAL);
1537
1538 return device_unref_and_replace(client->dev, dev);
1539}
1540
8301aa0b 1541static sd_dhcp6_client *dhcp6_client_free(sd_dhcp6_client *client) {
6f4490bb
YW
1542 if (!client)
1543 return NULL;
139b011a 1544
d0875a07 1545 sd_dhcp6_lease_unref(client->lease);
c9393e8c 1546
d0875a07
YW
1547 sd_event_source_disable_unref(client->receive_message);
1548 sd_event_source_disable_unref(client->timeout_resend);
3bb18e70 1549 sd_event_source_disable_unref(client->timeout_expire);
d0875a07
YW
1550 sd_event_source_disable_unref(client->timeout_t1);
1551 sd_event_source_disable_unref(client->timeout_t2);
6f4490bb 1552 sd_event_unref(client->event);
3733eec3 1553
10a0f27b
PF
1554 client->fd = safe_close(client->fd);
1555
793178b9
YW
1556 sd_device_unref(client->dev);
1557
3733eec3 1558 free(client->req_opts);
8006aa32 1559 free(client->fqdn);
de8d6e55 1560 free(client->mudurl);
877bfc78 1561 dhcp6_ia_clear_addresses(&client->ia_pd);
e7d5fe17 1562 ordered_hashmap_free(client->extra_options);
9e4dee4c 1563 ordered_set_free(client->vendor_options);
33923925 1564 strv_free(client->user_class);
73c8ced7 1565 strv_free(client->vendor_class);
61a9fa8f 1566 free(client->ifname);
33923925 1567
6b430fdb 1568 return mfree(client);
139b011a
PF
1569}
1570
8301aa0b
YW
1571DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_client, sd_dhcp6_client, dhcp6_client_free);
1572
0ae0e5cd 1573int sd_dhcp6_client_new(sd_dhcp6_client **ret) {
4afd3348 1574 _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL;
139b011a
PF
1575
1576 assert_return(ret, -EINVAL);
1577
8b8ecac8
YW
1578 client = new(sd_dhcp6_client, 1);
1579 if (!client)
da6fe470
PF
1580 return -ENOMEM;
1581
8b8ecac8
YW
1582 *client = (sd_dhcp6_client) {
1583 .n_ref = 1,
1584 .ia_na.type = SD_DHCP6_OPTION_IA_NA,
1585 .ia_pd.type = SD_DHCP6_OPTION_IA_PD,
1586 .ifindex = -1,
01b4e90f 1587 .request_ia = DHCP6_REQUEST_IA_NA | DHCP6_REQUEST_IA_PD,
254d1313 1588 .fd = -EBADF,
4397967f 1589 .rapid_commit = true,
8b8ecac8 1590 };
da6fe470 1591
1cc6c93a 1592 *ret = TAKE_PTR(client);
139b011a
PF
1593
1594 return 0;
1595}