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