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