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