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