]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/sd-dhcp6-client.c
util: move logind_running() to login-util.[ch]
[thirdparty/systemd.git] / src / libsystemd-network / sd-dhcp6-client.c
CommitLineData
139b011a
PF
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
7bd8e95d 6 Copyright (C) 2014-2015 Intel Corporation. All rights reserved.
139b011a
PF
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
22#include <errno.h>
23#include <string.h>
631bbe71 24#include <sys/ioctl.h>
76253e73 25#include <linux/if_infiniband.h>
139b011a 26
139b011a 27#include "sd-dhcp6-client.h"
07630cea
LP
28
29#include "dhcp-identifier.h"
f12abb48 30#include "dhcp6-internal.h"
631bbe71 31#include "dhcp6-lease-internal.h"
07630cea
LP
32#include "dhcp6-protocol.h"
33#include "network-internal.h"
34#include "random-util.h"
35#include "util.h"
139b011a 36
76253e73
DW
37#define MAX_MAC_ADDR_LEN INFINIBAND_ALEN
38
139b011a 39struct sd_dhcp6_client {
3733eec3 40 unsigned n_ref;
139b011a
PF
41
42 enum DHCP6State state;
43 sd_event *event;
44 int event_priority;
45 int index;
76253e73
DW
46 uint8_t mac_addr[MAX_MAC_ADDR_LEN];
47 size_t mac_addr_len;
48 uint16_t arp_type;
f12abb48 49 DHCP6IA ia_na;
a9aff361 50 be32_t transaction_id;
346e13a2 51 usec_t transaction_start;
631bbe71 52 struct sd_dhcp6_lease *lease;
a9aff361 53 int fd;
bbfa43ca 54 bool information_request;
da6fe470
PF
55 be16_t *req_opts;
56 size_t req_opts_allocated;
57 size_t req_opts_len;
a9aff361 58 sd_event_source *receive_message;
d1b0afe3
PF
59 usec_t retransmit_time;
60 uint8_t retransmit_count;
61 sd_event_source *timeout_resend;
62 sd_event_source *timeout_resend_expire;
139b011a
PF
63 sd_dhcp6_client_cb_t cb;
64 void *userdata;
764aad62 65 struct duid duid;
66eac120 66 size_t duid_len;
139b011a
PF
67};
68
da6fe470
PF
69static const uint16_t default_req_opts[] = {
70 DHCP6_OPTION_DNS_SERVERS,
71 DHCP6_OPTION_DOMAIN_LIST,
72 DHCP6_OPTION_NTP_SERVER,
41e4615d 73 DHCP6_OPTION_SNTP_SERVERS,
da6fe470
PF
74};
75
a9aff361
PF
76const char * dhcp6_message_type_table[_DHCP6_MESSAGE_MAX] = {
77 [DHCP6_SOLICIT] = "SOLICIT",
78 [DHCP6_ADVERTISE] = "ADVERTISE",
79 [DHCP6_REQUEST] = "REQUEST",
80 [DHCP6_CONFIRM] = "CONFIRM",
81 [DHCP6_RENEW] = "RENEW",
82 [DHCP6_REBIND] = "REBIND",
83 [DHCP6_REPLY] = "REPLY",
84 [DHCP6_RELEASE] = "RELEASE",
85 [DHCP6_DECLINE] = "DECLINE",
86 [DHCP6_RECONFIGURE] = "RECONFIGURE",
87 [DHCP6_INFORMATION_REQUEST] = "INFORMATION-REQUEST",
88 [DHCP6_RELAY_FORW] = "RELAY-FORW",
89 [DHCP6_RELAY_REPL] = "RELAY-REPL",
90};
91
92DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int);
93
631bbe71
PF
94const char * dhcp6_message_status_table[_DHCP6_STATUS_MAX] = {
95 [DHCP6_STATUS_SUCCESS] = "Success",
96 [DHCP6_STATUS_UNSPEC_FAIL] = "Unspecified failure",
97 [DHCP6_STATUS_NO_ADDRS_AVAIL] = "No addresses available",
98 [DHCP6_STATUS_NO_BINDING] = "Binding unavailable",
99 [DHCP6_STATUS_NOT_ON_LINK] = "Not on link",
100 [DHCP6_STATUS_USE_MULTICAST] = "Use multicast",
101};
102
103DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int);
104
3f0c075f
PF
105DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_client*, sd_dhcp6_client_unref);
106#define _cleanup_dhcp6_client_unref_ _cleanup_(sd_dhcp6_client_unrefp)
107
108#define DHCP6_CLIENT_DONT_DESTROY(client) \
109 _cleanup_dhcp6_client_unref_ _unused_ sd_dhcp6_client *_dont_destroy_##client = sd_dhcp6_client_ref(client)
110
c3e2adea
PF
111static int client_start(sd_dhcp6_client *client, enum DHCP6State state);
112
0ae0e5cd 113int sd_dhcp6_client_set_callback(sd_dhcp6_client *client, sd_dhcp6_client_cb_t cb, void *userdata) {
139b011a
PF
114 assert_return(client, -EINVAL);
115
116 client->cb = cb;
117 client->userdata = userdata;
118
119 return 0;
120}
121
0ae0e5cd 122int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index) {
139b011a
PF
123 assert_return(client, -EINVAL);
124 assert_return(interface_index >= -1, -EINVAL);
125
d7c9c21f
PF
126 assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
127
139b011a
PF
128 client->index = interface_index;
129
130 return 0;
131}
132
0ae0e5cd
LP
133int sd_dhcp6_client_set_mac(
134 sd_dhcp6_client *client,
135 const uint8_t *addr, size_t addr_len,
136 uint16_t arp_type) {
137
139b011a 138 assert_return(client, -EINVAL);
76253e73
DW
139 assert_return(addr, -EINVAL);
140 assert_return(addr_len > 0 && addr_len <= MAX_MAC_ADDR_LEN, -EINVAL);
141 assert_return(arp_type > 0, -EINVAL);
142
d7c9c21f
PF
143 assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
144
76253e73
DW
145 if (arp_type == ARPHRD_ETHER)
146 assert_return(addr_len == ETH_ALEN, -EINVAL);
147 else if (arp_type == ARPHRD_INFINIBAND)
148 assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
139b011a 149 else
76253e73
DW
150 return -EINVAL;
151
152 if (client->mac_addr_len == addr_len &&
153 memcmp(&client->mac_addr, addr, addr_len) == 0)
154 return 0;
155
156 memcpy(&client->mac_addr, addr, addr_len);
157 client->mac_addr_len = addr_len;
158 client->arp_type = arp_type;
139b011a
PF
159
160 return 0;
161}
162
0ae0e5cd 163static int client_ensure_duid(sd_dhcp6_client *client) {
cc22955c
TH
164 if (client->duid_len != 0)
165 return 0;
0ae0e5cd 166
cc22955c
TH
167 return dhcp_identifier_set_duid_en(&client->duid, &client->duid_len);
168}
169
0ae0e5cd
LP
170int sd_dhcp6_client_set_duid(
171 sd_dhcp6_client *client,
172 uint16_t type,
173 uint8_t *duid, size_t duid_len) {
66eac120
DW
174 assert_return(client, -EINVAL);
175 assert_return(duid, -EINVAL);
176 assert_return(duid_len > 0 && duid_len <= MAX_DUID_LEN, -EINVAL);
177
d7c9c21f
PF
178 assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
179
fe4b2156
TG
180 switch (type) {
181 case DHCP6_DUID_LLT:
764aad62 182 if (duid_len <= sizeof(client->duid.llt))
fe4b2156
TG
183 return -EINVAL;
184 break;
185 case DHCP6_DUID_EN:
764aad62 186 if (duid_len != sizeof(client->duid.en))
fe4b2156
TG
187 return -EINVAL;
188 break;
189 case DHCP6_DUID_LL:
764aad62 190 if (duid_len <= sizeof(client->duid.ll))
fe4b2156
TG
191 return -EINVAL;
192 break;
193 case DHCP6_DUID_UUID:
764aad62 194 if (duid_len != sizeof(client->duid.uuid))
fe4b2156
TG
195 return -EINVAL;
196 break;
197 default:
198 /* accept unknown type in order to be forward compatible */
199 break;
200 }
201
764aad62 202 client->duid.type = htobe16(type);
ebe207d4 203 memcpy(&client->duid.raw.data, duid, duid_len);
764aad62 204 client->duid_len = duid_len + sizeof(client->duid.type);
66eac120
DW
205
206 return 0;
207}
208
0ae0e5cd 209int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, bool enabled) {
bbfa43ca
PF
210 assert_return(client, -EINVAL);
211
d7c9c21f
PF
212 assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
213
bbfa43ca
PF
214 client->information_request = enabled;
215
216 return 0;
217}
218
0ae0e5cd 219int sd_dhcp6_client_get_information_request(sd_dhcp6_client *client, bool *enabled) {
bbfa43ca
PF
220 assert_return(client, -EINVAL);
221 assert_return(enabled, -EINVAL);
222
223 *enabled = client->information_request;
224
225 return 0;
226}
227
0ae0e5cd 228int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, uint16_t option) {
da6fe470
PF
229 size_t t;
230
231 assert_return(client, -EINVAL);
232 assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
233
234 switch(option) {
235 case DHCP6_OPTION_DNS_SERVERS:
236 case DHCP6_OPTION_DOMAIN_LIST:
237 case DHCP6_OPTION_SNTP_SERVERS:
238 case DHCP6_OPTION_NTP_SERVER:
239 break;
240
241 default:
242 return -EINVAL;
243 }
244
245 for (t = 0; t < client->req_opts_len; t++)
246 if (client->req_opts[t] == htobe16(option))
247 return -EEXIST;
248
249 if (!GREEDY_REALLOC(client->req_opts, client->req_opts_allocated,
250 client->req_opts_len + 1))
251 return -ENOMEM;
252
253 client->req_opts[client->req_opts_len++] = htobe16(option);
254
255 return 0;
256}
257
ea3b3a75
PF
258int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
259 assert_return(client, -EINVAL);
260 assert_return(ret, -EINVAL);
261
262 if (!client->lease)
263 return -ENOMSG;
264
e6b18ffa 265 *ret = client->lease;
ea3b3a75
PF
266
267 return 0;
268}
269
3f0c075f
PF
270static void client_notify(sd_dhcp6_client *client, int event) {
271 if (client->cb)
139b011a 272 client->cb(client, event, client->userdata);
139b011a
PF
273}
274
f8908727 275static void client_set_lease(sd_dhcp6_client *client, sd_dhcp6_lease *lease) {
4e3e6679
PF
276 if (client->lease) {
277 dhcp6_lease_clear_timers(&client->lease->ia);
f8908727 278 sd_dhcp6_lease_unref(client->lease);
4e3e6679 279 }
f8908727
TH
280 client->lease = lease;
281}
282
283static int client_reset(sd_dhcp6_client *client) {
284 assert_return(client, -EINVAL);
285
286 client_set_lease(client, NULL);
4e3e6679 287
a9aff361
PF
288 client->receive_message =
289 sd_event_source_unref(client->receive_message);
290
c806ffb9 291 client->fd = safe_close(client->fd);
a9aff361 292
c3e2adea 293 client->transaction_id = 0;
346e13a2 294 client->transaction_start = 0;
a9aff361 295
f12abb48
PF
296 client->ia_na.timeout_t1 =
297 sd_event_source_unref(client->ia_na.timeout_t1);
298 client->ia_na.timeout_t2 =
299 sd_event_source_unref(client->ia_na.timeout_t2);
300
d1b0afe3
PF
301 client->retransmit_time = 0;
302 client->retransmit_count = 0;
303 client->timeout_resend = sd_event_source_unref(client->timeout_resend);
304 client->timeout_resend_expire =
305 sd_event_source_unref(client->timeout_resend_expire);
306
139b011a
PF
307 client->state = DHCP6_STATE_STOPPED;
308
309 return 0;
310}
311
3f0c075f
PF
312static void client_stop(sd_dhcp6_client *client, int error) {
313 DHCP6_CLIENT_DONT_DESTROY(client);
139b011a 314
3f0c075f 315 assert(client);
139b011a 316
3f0c075f
PF
317 client_notify(client, error);
318
319 client_reset(client);
139b011a
PF
320}
321
346e13a2 322static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
a9aff361
PF
323 _cleanup_free_ DHCP6Message *message = NULL;
324 struct in6_addr all_servers =
325 IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
326 size_t len, optlen = 512;
327 uint8_t *opt;
328 int r;
346e13a2
PF
329 usec_t elapsed_usec;
330 be16_t elapsed_time;
a9aff361
PF
331
332 len = sizeof(DHCP6Message) + optlen;
333
334 message = malloc0(len);
335 if (!message)
336 return -ENOMEM;
337
338 opt = (uint8_t *)(message + 1);
339
340 message->transaction_id = client->transaction_id;
341
342 switch(client->state) {
bbfa43ca
PF
343 case DHCP6_STATE_INFORMATION_REQUEST:
344 message->type = DHCP6_INFORMATION_REQUEST;
345
346 break;
347
a9aff361
PF
348 case DHCP6_STATE_SOLICITATION:
349 message->type = DHCP6_SOLICIT;
350
ed6ee219
PF
351 r = dhcp6_option_append(&opt, &optlen,
352 DHCP6_OPTION_RAPID_COMMIT, 0, NULL);
926695f1
TA
353 if (r < 0)
354 return r;
ed6ee219 355
7246333c 356 r = dhcp6_option_append_ia(&opt, &optlen, &client->ia_na);
a9aff361
PF
357 if (r < 0)
358 return r;
359
7246333c
PF
360 break;
361
362 case DHCP6_STATE_REQUEST:
3dc34fcc
PF
363 case DHCP6_STATE_RENEW:
364
365 if (client->state == DHCP6_STATE_REQUEST)
366 message->type = DHCP6_REQUEST;
367 else
368 message->type = DHCP6_RENEW;
7246333c
PF
369
370 r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_SERVERID,
371 client->lease->serverid_len,
372 client->lease->serverid);
373 if (r < 0)
374 return r;
375
376 r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia);
a9aff361
PF
377 if (r < 0)
378 return r;
379
380 break;
381
3dc34fcc
PF
382 case DHCP6_STATE_REBIND:
383 message->type = DHCP6_REBIND;
384
385 r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia);
386 if (r < 0)
387 return r;
388
389 break;
390
a9aff361 391 case DHCP6_STATE_STOPPED:
a34b57c0 392 case DHCP6_STATE_BOUND:
a9aff361
PF
393 return -EINVAL;
394 }
395
da6fe470
PF
396 r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_ORO,
397 client->req_opts_len * sizeof(be16_t),
398 client->req_opts);
399 if (r < 0)
400 return r;
401
cc22955c 402 assert (client->duid_len);
7246333c 403 r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_CLIENTID,
66eac120 404 client->duid_len, &client->duid);
7246333c
PF
405 if (r < 0)
406 return r;
407
346e13a2
PF
408 elapsed_usec = time_now - client->transaction_start;
409 if (elapsed_usec < 0xffff * USEC_PER_MSEC * 10)
410 elapsed_time = htobe16(elapsed_usec / USEC_PER_MSEC / 10);
411 else
412 elapsed_time = 0xffff;
413
414 r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_ELAPSED_TIME,
415 sizeof(elapsed_time), &elapsed_time);
416 if (r < 0)
417 return r;
418
a9aff361
PF
419 r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message,
420 len - optlen);
421 if (r < 0)
422 return r;
423
424 log_dhcp6_client(client, "Sent %s",
425 dhcp6_message_type_to_string(message->type));
426
427 return 0;
428}
429
a34b57c0
PF
430static int client_timeout_t2(sd_event_source *s, uint64_t usec,
431 void *userdata) {
432 sd_dhcp6_client *client = userdata;
433
434 assert_return(s, -EINVAL);
435 assert_return(client, -EINVAL);
436 assert_return(client->lease, -EINVAL);
437
438 client->lease->ia.timeout_t2 =
439 sd_event_source_unref(client->lease->ia.timeout_t2);
440
441 log_dhcp6_client(client, "Timeout T2");
442
3dc34fcc
PF
443 client_start(client, DHCP6_STATE_REBIND);
444
a34b57c0
PF
445 return 0;
446}
447
448static int client_timeout_t1(sd_event_source *s, uint64_t usec,
449 void *userdata) {
450 sd_dhcp6_client *client = userdata;
451
452 assert_return(s, -EINVAL);
453 assert_return(client, -EINVAL);
454 assert_return(client->lease, -EINVAL);
455
456 client->lease->ia.timeout_t1 =
457 sd_event_source_unref(client->lease->ia.timeout_t1);
458
459 log_dhcp6_client(client, "Timeout T1");
460
3dc34fcc
PF
461 client_start(client, DHCP6_STATE_RENEW);
462
a34b57c0
PF
463 return 0;
464}
465
d1b0afe3
PF
466static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec,
467 void *userdata) {
468 sd_dhcp6_client *client = userdata;
3dc34fcc
PF
469 DHCP6_CLIENT_DONT_DESTROY(client);
470 enum DHCP6State state;
d1b0afe3
PF
471
472 assert(s);
473 assert(client);
474 assert(client->event);
475
3dc34fcc
PF
476 state = client->state;
477
10c9ce61 478 client_stop(client, SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE);
d1b0afe3 479
3dc34fcc
PF
480 /* RFC 3315, section 18.1.4., says that "...the client may choose to
481 use a Solicit message to locate a new DHCP server..." */
482 if (state == DHCP6_STATE_REBIND)
483 client_start(client, DHCP6_STATE_SOLICITATION);
484
d1b0afe3
PF
485 return 0;
486}
487
488static usec_t client_timeout_compute_random(usec_t val) {
489 return val - val / 10 +
490 (random_u32() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
491}
492
493static int client_timeout_resend(sd_event_source *s, uint64_t usec,
494 void *userdata) {
495 int r = 0;
496 sd_dhcp6_client *client = userdata;
4b4923e6 497 usec_t time_now, init_retransmit_time = 0, max_retransmit_time = 0;
5e913450 498 usec_t max_retransmit_duration = 0;
513a6fa8 499 uint8_t max_retransmit_count = 0;
d1b0afe3 500 char time_string[FORMAT_TIMESPAN_MAX];
3dc34fcc 501 uint32_t expire = 0;
d1b0afe3
PF
502
503 assert(s);
504 assert(client);
505 assert(client->event);
506
507 client->timeout_resend = sd_event_source_unref(client->timeout_resend);
508
509 switch (client->state) {
bbfa43ca
PF
510 case DHCP6_STATE_INFORMATION_REQUEST:
511 init_retransmit_time = DHCP6_INF_TIMEOUT;
512 max_retransmit_time = DHCP6_INF_MAX_RT;
513
514 break;
515
d1b0afe3 516 case DHCP6_STATE_SOLICITATION:
7246333c
PF
517
518 if (client->retransmit_count && client->lease) {
519 client_start(client, DHCP6_STATE_REQUEST);
520 return 0;
521 }
522
d1b0afe3
PF
523 init_retransmit_time = DHCP6_SOL_TIMEOUT;
524 max_retransmit_time = DHCP6_SOL_MAX_RT;
d1b0afe3
PF
525
526 break;
527
7246333c
PF
528 case DHCP6_STATE_REQUEST:
529 init_retransmit_time = DHCP6_REQ_TIMEOUT;
530 max_retransmit_time = DHCP6_REQ_MAX_RT;
531 max_retransmit_count = DHCP6_REQ_MAX_RC;
7246333c
PF
532
533 break;
534
3dc34fcc
PF
535 case DHCP6_STATE_RENEW:
536 init_retransmit_time = DHCP6_REN_TIMEOUT;
537 max_retransmit_time = DHCP6_REN_MAX_RT;
3dc34fcc
PF
538
539 /* RFC 3315, section 18.1.3. says max retransmit duration will
540 be the remaining time until T2. Instead of setting MRD,
541 wait for T2 to trigger with the same end result */
3dc34fcc
PF
542
543 break;
544
545 case DHCP6_STATE_REBIND:
546 init_retransmit_time = DHCP6_REB_TIMEOUT;
547 max_retransmit_time = DHCP6_REB_MAX_RT;
3dc34fcc
PF
548
549 if (!client->timeout_resend_expire) {
550 r = dhcp6_lease_ia_rebind_expire(&client->lease->ia,
551 &expire);
552 if (r < 0) {
553 client_stop(client, r);
554 return 0;
555 }
556 max_retransmit_duration = expire * USEC_PER_SEC;
557 }
558
559 break;
560
d1b0afe3 561 case DHCP6_STATE_STOPPED:
a34b57c0 562 case DHCP6_STATE_BOUND:
d1b0afe3
PF
563 return 0;
564 }
565
566 if (max_retransmit_count &&
567 client->retransmit_count >= max_retransmit_count) {
10c9ce61 568 client_stop(client, SD_DHCP6_CLIENT_EVENT_RETRANS_MAX);
d1b0afe3
PF
569 return 0;
570 }
571
fa94c34b 572 r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
d1b0afe3
PF
573 if (r < 0)
574 goto error;
575
346e13a2
PF
576 r = client_send_message(client, time_now);
577 if (r >= 0)
578 client->retransmit_count++;
579
d1b0afe3
PF
580 if (!client->retransmit_time) {
581 client->retransmit_time =
582 client_timeout_compute_random(init_retransmit_time);
a9aff361
PF
583
584 if (client->state == DHCP6_STATE_SOLICITATION)
585 client->retransmit_time += init_retransmit_time / 10;
586
d1b0afe3
PF
587 } else {
588 if (max_retransmit_time &&
589 client->retransmit_time > max_retransmit_time / 2)
590 client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
591 else
592 client->retransmit_time += client_timeout_compute_random(client->retransmit_time);
593 }
594
595 log_dhcp6_client(client, "Next retransmission in %s",
ed19c567 596 format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->retransmit_time, USEC_PER_SEC));
d1b0afe3
PF
597
598 r = sd_event_add_time(client->event, &client->timeout_resend,
fa94c34b 599 clock_boottime_or_monotonic(),
d1b0afe3
PF
600 time_now + client->retransmit_time,
601 10 * USEC_PER_MSEC, client_timeout_resend,
602 client);
603 if (r < 0)
604 goto error;
605
606 r = sd_event_source_set_priority(client->timeout_resend,
607 client->event_priority);
608 if (r < 0)
609 goto error;
610
356779df 611 r = sd_event_source_set_description(client->timeout_resend, "dhcp6-resend-timer");
9021bb9f
TG
612 if (r < 0)
613 goto error;
614
d1b0afe3
PF
615 if (max_retransmit_duration && !client->timeout_resend_expire) {
616
617 log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs",
618 max_retransmit_duration / USEC_PER_SEC);
619
620 r = sd_event_add_time(client->event,
621 &client->timeout_resend_expire,
fa94c34b 622 clock_boottime_or_monotonic(),
d1b0afe3
PF
623 time_now + max_retransmit_duration,
624 USEC_PER_SEC,
625 client_timeout_resend_expire, client);
626 if (r < 0)
627 goto error;
628
629 r = sd_event_source_set_priority(client->timeout_resend_expire,
630 client->event_priority);
631 if (r < 0)
632 goto error;
9021bb9f 633
356779df 634 r = sd_event_source_set_description(client->timeout_resend_expire, "dhcp6-resend-expire-timer");
9021bb9f
TG
635 if (r < 0)
636 goto error;
d1b0afe3
PF
637 }
638
639error:
640 if (r < 0)
641 client_stop(client, r);
642
643 return 0;
644}
645
f12abb48 646static int client_ensure_iaid(sd_dhcp6_client *client) {
cfb5b380 647 int r;
f12abb48
PF
648
649 assert(client);
650
651 if (client->ia_na.id)
652 return 0;
653
cfb5b380
TG
654 r = dhcp_identifier_set_iaid(client->index, client->mac_addr, client->mac_addr_len, &client->ia_na.id);
655 if (r < 0)
656 return r;
f12abb48
PF
657
658 return 0;
659}
660
631bbe71
PF
661static int client_parse_message(sd_dhcp6_client *client,
662 DHCP6Message *message, size_t len,
663 sd_dhcp6_lease *lease) {
664 int r;
44481a8b 665 uint8_t *optval, *option, *id = NULL;
631bbe71
PF
666 uint16_t optcode, status;
667 size_t optlen, id_len;
668 bool clientid = false;
669 be32_t iaid_lease;
670
44481a8b
ZJS
671 option = (uint8_t *)message + sizeof(DHCP6Message);
672 len -= sizeof(DHCP6Message);
673
631bbe71
PF
674 while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen,
675 &optval)) >= 0) {
676 switch (optcode) {
677 case DHCP6_OPTION_CLIENTID:
678 if (clientid) {
679 log_dhcp6_client(client, "%s contains multiple clientids",
680 dhcp6_message_type_to_string(message->type));
681 return -EINVAL;
682 }
683
66eac120 684 if (optlen != client->duid_len ||
631bbe71
PF
685 memcmp(&client->duid, optval, optlen) != 0) {
686 log_dhcp6_client(client, "%s DUID does not match",
687 dhcp6_message_type_to_string(message->type));
688
689 return -EINVAL;
690 }
691 clientid = true;
692
693 break;
694
695 case DHCP6_OPTION_SERVERID:
696 r = dhcp6_lease_get_serverid(lease, &id, &id_len);
697 if (r >= 0 && id) {
698 log_dhcp6_client(client, "%s contains multiple serverids",
699 dhcp6_message_type_to_string(message->type));
700 return -EINVAL;
701 }
702
703 r = dhcp6_lease_set_serverid(lease, optval, optlen);
704 if (r < 0)
705 return r;
706
707 break;
708
709 case DHCP6_OPTION_PREFERENCE:
710 if (optlen != 1)
711 return -EINVAL;
712
713 r = dhcp6_lease_set_preference(lease, *optval);
714 if (r < 0)
715 return r;
716
717 break;
718
719 case DHCP6_OPTION_STATUS_CODE:
720 if (optlen < 2)
721 return -EINVAL;
722
723 status = optval[0] << 8 | optval[1];
724 if (status) {
725 log_dhcp6_client(client, "%s Status %s",
726 dhcp6_message_type_to_string(message->type),
727 dhcp6_message_status_to_string(status));
728 return -EINVAL;
729 }
730
731 break;
732
733 case DHCP6_OPTION_IA_NA:
bbfa43ca
PF
734 if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
735 log_dhcp6_client(client, "Information request ignoring IA NA option");
736
737 break;
738 }
739
631bbe71
PF
740 r = dhcp6_option_parse_ia(&optval, &optlen, optcode,
741 &lease->ia);
742 if (r < 0 && r != -ENOMSG)
743 return r;
744
745 r = dhcp6_lease_get_iaid(lease, &iaid_lease);
746 if (r < 0)
747 return r;
748
749 if (client->ia_na.id != iaid_lease) {
750 log_dhcp6_client(client, "%s has wrong IAID",
751 dhcp6_message_type_to_string(message->type));
752 return -EINVAL;
753 }
754
755 break;
ed6ee219
PF
756
757 case DHCP6_OPTION_RAPID_COMMIT:
758 r = dhcp6_lease_set_rapid_commit(lease);
759 if (r < 0)
760 return r;
761
762 break;
7bd8e95d
PF
763
764 case DHCP6_OPTION_DNS_SERVERS:
765 r = dhcp6_lease_set_dns(lease, optval, optlen);
766 if (r < 0)
767 return r;
768
769 break;
5da1b97f
PF
770
771 case DHCP6_OPTION_DOMAIN_LIST:
772 r = dhcp6_lease_set_domains(lease, optval, optlen);
773 if (r < 0)
774 return r;
775
776 break;
777
6599680e
PF
778 case DHCP6_OPTION_NTP_SERVER:
779 r = dhcp6_lease_set_ntp(lease, optval, optlen);
780 if (r < 0)
781 return r;
782
783 break;
41e4615d
PF
784
785 case DHCP6_OPTION_SNTP_SERVERS:
786 r = dhcp6_lease_set_sntp(lease, optval, optlen);
787 if (r < 0)
788 return r;
789
790 break;
631bbe71 791 }
6599680e 792
631bbe71
PF
793 }
794
c47e8936
PF
795 if (r == -ENOMSG)
796 r = 0;
797
798 if (r < 0 || !clientid) {
631bbe71
PF
799 log_dhcp6_client(client, "%s has incomplete options",
800 dhcp6_message_type_to_string(message->type));
801 return -EINVAL;
802 }
803
bbfa43ca
PF
804 if (client->state != DHCP6_STATE_INFORMATION_REQUEST) {
805 r = dhcp6_lease_get_serverid(lease, &id, &id_len);
806 if (r < 0)
807 log_dhcp6_client(client, "%s has no server id",
808 dhcp6_message_type_to_string(message->type));
809 }
631bbe71
PF
810
811 return r;
812}
813
0ae0e5cd 814static int client_receive_reply(sd_dhcp6_client *client, DHCP6Message *reply, size_t len) {
a34b57c0
PF
815 int r;
816 _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL;
ed6ee219 817 bool rapid_commit;
a34b57c0
PF
818
819 if (reply->type != DHCP6_REPLY)
ed6ee219 820 return 0;
a34b57c0
PF
821
822 r = dhcp6_lease_new(&lease);
823 if (r < 0)
824 return -ENOMEM;
825
826 r = client_parse_message(client, reply, len, lease);
827 if (r < 0)
828 return r;
829
ed6ee219
PF
830 if (client->state == DHCP6_STATE_SOLICITATION) {
831 r = dhcp6_lease_get_rapid_commit(lease, &rapid_commit);
832 if (r < 0)
833 return r;
834
835 if (!rapid_commit)
836 return 0;
837 }
838
f8908727 839 client_set_lease(client, lease);
9d89d1ae 840 lease = NULL;
a34b57c0
PF
841
842 return DHCP6_STATE_BOUND;
843}
844
0ae0e5cd 845static int client_receive_advertise(sd_dhcp6_client *client, DHCP6Message *advertise, size_t len) {
631bbe71
PF
846 int r;
847 _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL;
848 uint8_t pref_advertise = 0, pref_lease = 0;
849
850 if (advertise->type != DHCP6_ADVERTISE)
ed6ee219 851 return 0;
631bbe71
PF
852
853 r = dhcp6_lease_new(&lease);
854 if (r < 0)
855 return r;
856
857 r = client_parse_message(client, advertise, len, lease);
858 if (r < 0)
859 return r;
860
861 r = dhcp6_lease_get_preference(lease, &pref_advertise);
862 if (r < 0)
863 return r;
864
865 r = dhcp6_lease_get_preference(client->lease, &pref_lease);
bbfa43ca
PF
866
867 if (r < 0 || pref_advertise > pref_lease) {
f8908727 868 client_set_lease(client, lease);
631bbe71
PF
869 lease = NULL;
870 r = 0;
871 }
872
7246333c
PF
873 if (pref_advertise == 255 || client->retransmit_count > 1)
874 r = DHCP6_STATE_REQUEST;
875
631bbe71
PF
876 return r;
877}
878
0ae0e5cd 879static int client_receive_message(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
631bbe71 880 sd_dhcp6_client *client = userdata;
3f0c075f 881 DHCP6_CLIENT_DONT_DESTROY(client);
631bbe71
PF
882 _cleanup_free_ DHCP6Message *message;
883 int r, buflen, len;
884
885 assert(s);
886 assert(client);
887 assert(client->event);
888
889 r = ioctl(fd, FIONREAD, &buflen);
890 if (r < 0 || buflen <= 0)
891 buflen = DHCP6_MIN_OPTIONS_SIZE;
892
893 message = malloc0(buflen);
894 if (!message)
895 return -ENOMEM;
896
897 len = read(fd, message, buflen);
898 if ((size_t)len < sizeof(DHCP6Message)) {
6ec60d20 899 log_dhcp6_client(client, "could not receive message from UDP socket: %m");
631bbe71
PF
900 return 0;
901 }
902
903 switch(message->type) {
904 case DHCP6_SOLICIT:
905 case DHCP6_REQUEST:
906 case DHCP6_CONFIRM:
907 case DHCP6_RENEW:
908 case DHCP6_REBIND:
909 case DHCP6_RELEASE:
910 case DHCP6_DECLINE:
911 case DHCP6_INFORMATION_REQUEST:
912 case DHCP6_RELAY_FORW:
913 case DHCP6_RELAY_REPL:
914 return 0;
915
916 case DHCP6_ADVERTISE:
917 case DHCP6_REPLY:
918 case DHCP6_RECONFIGURE:
919 break;
920
921 default:
922 log_dhcp6_client(client, "unknown message type %d",
923 message->type);
924 return 0;
925 }
926
927 if (client->transaction_id != (message->transaction_id &
928 htobe32(0x00ffffff)))
929 return 0;
930
931 switch (client->state) {
bbfa43ca
PF
932 case DHCP6_STATE_INFORMATION_REQUEST:
933 r = client_receive_reply(client, message, len);
934 if (r < 0)
935 return 0;
936
10c9ce61 937 client_notify(client, SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST);
bbfa43ca
PF
938
939 client_start(client, DHCP6_STATE_STOPPED);
940
941 break;
942
631bbe71
PF
943 case DHCP6_STATE_SOLICITATION:
944 r = client_receive_advertise(client, message, len);
945
ed6ee219 946 if (r == DHCP6_STATE_REQUEST) {
7246333c
PF
947 client_start(client, r);
948
ed6ee219
PF
949 break;
950 }
631bbe71 951
ed6ee219 952 /* fall through for Soliciation Rapid Commit option check */
7246333c 953 case DHCP6_STATE_REQUEST:
3dc34fcc
PF
954 case DHCP6_STATE_RENEW:
955 case DHCP6_STATE_REBIND:
956
a34b57c0
PF
957 r = client_receive_reply(client, message, len);
958 if (r < 0)
959 return 0;
960
961 if (r == DHCP6_STATE_BOUND) {
962
963 r = client_start(client, DHCP6_STATE_BOUND);
964 if (r < 0) {
965 client_stop(client, r);
966 return 0;
967 }
968
10c9ce61 969 client_notify(client, SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE);
a34b57c0
PF
970 }
971
972 break;
973
974 case DHCP6_STATE_BOUND:
975
976 break;
977
631bbe71 978 case DHCP6_STATE_STOPPED:
631bbe71
PF
979 return 0;
980 }
981
982 if (r >= 0) {
983 log_dhcp6_client(client, "Recv %s",
984 dhcp6_message_type_to_string(message->type));
985 }
986
a9aff361
PF
987 return 0;
988}
989
0ae0e5cd 990static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
f12abb48 991 int r;
a34b57c0
PF
992 usec_t timeout, time_now;
993 char time_string[FORMAT_TIMESPAN_MAX];
f12abb48
PF
994
995 assert_return(client, -EINVAL);
996 assert_return(client->event, -EINVAL);
997 assert_return(client->index > 0, -EINVAL);
c3e2adea 998 assert_return(client->state != state, -EINVAL);
f12abb48 999
c3e2adea
PF
1000 client->timeout_resend_expire =
1001 sd_event_source_unref(client->timeout_resend_expire);
1002 client->timeout_resend = sd_event_source_unref(client->timeout_resend);
1003 client->retransmit_time = 0;
1004 client->retransmit_count = 0;
f12abb48 1005
38a03f06
LP
1006 r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
1007 if (r < 0)
1008 return r;
346e13a2 1009
c3e2adea
PF
1010 switch (state) {
1011 case DHCP6_STATE_STOPPED:
bbfa43ca
PF
1012 if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
1013 client->state = DHCP6_STATE_STOPPED;
a9aff361 1014
bbfa43ca
PF
1015 return 0;
1016 }
9021bb9f 1017
bbfa43ca
PF
1018 /* fall through */
1019 case DHCP6_STATE_SOLICITATION:
c3e2adea
PF
1020 client->state = DHCP6_STATE_SOLICITATION;
1021
7246333c
PF
1022 break;
1023
bbfa43ca 1024 case DHCP6_STATE_INFORMATION_REQUEST:
7246333c 1025 case DHCP6_STATE_REQUEST:
3dc34fcc
PF
1026 case DHCP6_STATE_RENEW:
1027 case DHCP6_STATE_REBIND:
a34b57c0 1028
7246333c
PF
1029 client->state = state;
1030
c3e2adea 1031 break;
a34b57c0
PF
1032
1033 case DHCP6_STATE_BOUND:
1034
a34b57c0
PF
1035 if (client->lease->ia.lifetime_t1 == 0xffffffff ||
1036 client->lease->ia.lifetime_t2 == 0xffffffff) {
1037
1038 log_dhcp6_client(client, "infinite T1 0x%08x or T2 0x%08x",
1039 be32toh(client->lease->ia.lifetime_t1),
1040 be32toh(client->lease->ia.lifetime_t2));
1041
1042 return 0;
1043 }
1044
1045 timeout = client_timeout_compute_random(be32toh(client->lease->ia.lifetime_t1) * USEC_PER_SEC);
1046
1047 log_dhcp6_client(client, "T1 expires in %s",
ed19c567 1048 format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC));
a34b57c0
PF
1049
1050 r = sd_event_add_time(client->event,
1051 &client->lease->ia.timeout_t1,
fa94c34b 1052 clock_boottime_or_monotonic(), time_now + timeout,
a34b57c0
PF
1053 10 * USEC_PER_SEC, client_timeout_t1,
1054 client);
1055 if (r < 0)
1056 return r;
1057
1058 r = sd_event_source_set_priority(client->lease->ia.timeout_t1,
1059 client->event_priority);
1060 if (r < 0)
1061 return r;
1062
356779df 1063 r = sd_event_source_set_description(client->lease->ia.timeout_t1, "dhcp6-t1-timeout");
9021bb9f
TG
1064 if (r < 0)
1065 return r;
1066
a34b57c0
PF
1067 timeout = client_timeout_compute_random(be32toh(client->lease->ia.lifetime_t2) * USEC_PER_SEC);
1068
1069 log_dhcp6_client(client, "T2 expires in %s",
ed19c567 1070 format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC));
a34b57c0
PF
1071
1072 r = sd_event_add_time(client->event,
1073 &client->lease->ia.timeout_t2,
fa94c34b 1074 clock_boottime_or_monotonic(), time_now + timeout,
a34b57c0
PF
1075 10 * USEC_PER_SEC, client_timeout_t2,
1076 client);
1077 if (r < 0)
1078 return r;
1079
1080 r = sd_event_source_set_priority(client->lease->ia.timeout_t2,
1081 client->event_priority);
1082 if (r < 0)
1083 return r;
1084
356779df 1085 r = sd_event_source_set_description(client->lease->ia.timeout_t2, "dhcp6-t2-timeout");
9021bb9f
TG
1086 if (r < 0)
1087 return r;
1088
3dc34fcc
PF
1089 client->state = state;
1090
a34b57c0 1091 return 0;
c3e2adea 1092 }
a9aff361 1093
c3e2adea 1094 client->transaction_id = random_u32() & htobe32(0x00ffffff);
346e13a2 1095 client->transaction_start = time_now;
d1b0afe3
PF
1096
1097 r = sd_event_add_time(client->event, &client->timeout_resend,
fa94c34b 1098 clock_boottime_or_monotonic(), 0, 0, client_timeout_resend,
d1b0afe3
PF
1099 client);
1100 if (r < 0)
1101 return r;
1102
1103 r = sd_event_source_set_priority(client->timeout_resend,
1104 client->event_priority);
1105 if (r < 0)
1106 return r;
1107
356779df 1108 r = sd_event_source_set_description(client->timeout_resend, "dhcp6-resend-timeout");
9021bb9f
TG
1109 if (r < 0)
1110 return r;
1111
f12abb48
PF
1112 return 0;
1113}
1114
0ae0e5cd 1115int sd_dhcp6_client_stop(sd_dhcp6_client *client) {
10c9ce61 1116 client_stop(client, SD_DHCP6_CLIENT_EVENT_STOP);
139b011a
PF
1117
1118 return 0;
1119}
1120
0ae0e5cd 1121int sd_dhcp6_client_start(sd_dhcp6_client *client) {
139b011a 1122 int r = 0;
bbfa43ca 1123 enum DHCP6State state = DHCP6_STATE_SOLICITATION;
139b011a
PF
1124
1125 assert_return(client, -EINVAL);
1126 assert_return(client->event, -EINVAL);
1127 assert_return(client->index > 0, -EINVAL);
1128
d7c9c21f
PF
1129 if (!IN_SET(client->state, DHCP6_STATE_STOPPED))
1130 return -EALREADY;
1131
c806ffb9 1132 r = client_reset(client);
f12abb48
PF
1133 if (r < 0)
1134 return r;
1135
bbfa43ca
PF
1136 r = client_ensure_iaid(client);
1137 if (r < 0)
1138 return r;
1139
cc22955c
TH
1140 r = client_ensure_duid(client);
1141 if (r < 0)
1142 return r;
1143
bbfa43ca
PF
1144 r = dhcp6_network_bind_udp_socket(client->index, NULL);
1145 if (r < 0)
1146 return r;
1147
1148 client->fd = r;
1149
1150 r = sd_event_add_io(client->event, &client->receive_message,
1151 client->fd, EPOLLIN, client_receive_message,
1152 client);
1153 if (r < 0)
1154 goto error;
1155
1156 r = sd_event_source_set_priority(client->receive_message,
1157 client->event_priority);
1158 if (r < 0)
1159 goto error;
1160
1161 r = sd_event_source_set_description(client->receive_message,
1162 "dhcp6-receive-message");
1163 if (r < 0)
1164 goto error;
1165
1166 if (client->information_request)
1167 state = DHCP6_STATE_INFORMATION_REQUEST;
1168
1169 log_dhcp6_client(client, "Started in %s mode",
1170 client->information_request? "Information request":
1171 "Managed");
1172
1173 return client_start(client, state);
1174
1175error:
1176 client_reset(client);
1177 return r;
139b011a
PF
1178}
1179
0ae0e5cd 1180int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, int priority) {
139b011a
PF
1181 int r;
1182
1183 assert_return(client, -EINVAL);
1184 assert_return(!client->event, -EBUSY);
1185
1186 if (event)
1187 client->event = sd_event_ref(event);
1188 else {
1189 r = sd_event_default(&client->event);
1190 if (r < 0)
1191 return 0;
1192 }
1193
1194 client->event_priority = priority;
1195
1196 return 0;
1197}
1198
1199int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) {
1200 assert_return(client, -EINVAL);
1201
1202 client->event = sd_event_unref(client->event);
1203
1204 return 0;
1205}
1206
1207sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
1208 if (!client)
1209 return NULL;
1210
1211 return client->event;
1212}
1213
1214sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client) {
3733eec3
LP
1215
1216 if (!client)
1217 return NULL;
1218
1219 assert(client->n_ref >= 1);
1220 client->n_ref++;
139b011a
PF
1221
1222 return client;
1223}
1224
1225sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
139b011a 1226
3733eec3
LP
1227 if (!client)
1228 return NULL;
139b011a 1229
3733eec3
LP
1230 assert(client->n_ref >= 1);
1231 client->n_ref--;
139b011a 1232
3733eec3 1233 if (client->n_ref > 0)
139b011a 1234 return NULL;
139b011a 1235
3733eec3
LP
1236 client_reset(client);
1237
1238 sd_dhcp6_client_detach_event(client);
3733eec3
LP
1239
1240 free(client->req_opts);
1241 free(client);
1242
1243 return NULL;
139b011a
PF
1244}
1245
0ae0e5cd 1246int sd_dhcp6_client_new(sd_dhcp6_client **ret) {
3f0c075f 1247 _cleanup_dhcp6_client_unref_ sd_dhcp6_client *client = NULL;
da6fe470 1248 size_t t;
139b011a
PF
1249
1250 assert_return(ret, -EINVAL);
1251
1252 client = new0(sd_dhcp6_client, 1);
1253 if (!client)
1254 return -ENOMEM;
1255
3733eec3 1256 client->n_ref = 1;
139b011a 1257
f12abb48
PF
1258 client->ia_na.type = DHCP6_OPTION_IA_NA;
1259
139b011a
PF
1260 client->index = -1;
1261
c806ffb9
ZJS
1262 client->fd = -1;
1263
da6fe470
PF
1264 client->req_opts_len = ELEMENTSOF(default_req_opts);
1265
1266 client->req_opts = new0(be16_t, client->req_opts_len);
1267 if (!client->req_opts)
1268 return -ENOMEM;
1269
1270 for (t = 0; t < client->req_opts_len; t++)
1271 client->req_opts[t] = htobe16(default_req_opts[t]);
1272
139b011a
PF
1273 *ret = client;
1274 client = NULL;
1275
1276 return 0;
1277}