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