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