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