]>
Commit | Line | Data |
---|---|---|
139b011a PF |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright (C) 2014 Intel Corporation. All rights reserved. | |
7 | ||
8 | systemd is free software; you can redistribute it and/or modify it | |
9 | under the terms of the GNU Lesser General Public License as published by | |
10 | the Free Software Foundation; either version 2.1 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | systemd is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | Lesser General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU Lesser General Public License | |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
20 | ***/ | |
21 | ||
22 | #include <errno.h> | |
23 | #include <string.h> | |
631bbe71 | 24 | #include <sys/ioctl.h> |
139b011a | 25 | |
f12abb48 PF |
26 | #include "udev.h" |
27 | #include "udev-util.h" | |
28 | #include "virt.h" | |
a276e6d6 | 29 | #include "siphash24.h" |
139b011a PF |
30 | #include "util.h" |
31 | #include "refcnt.h" | |
32 | ||
f12abb48 | 33 | #include "network-internal.h" |
139b011a PF |
34 | #include "sd-dhcp6-client.h" |
35 | #include "dhcp6-protocol.h" | |
f12abb48 | 36 | #include "dhcp6-internal.h" |
631bbe71 | 37 | #include "dhcp6-lease-internal.h" |
139b011a | 38 | |
a276e6d6 TG |
39 | #define SYSTEMD_PEN 43793 |
40 | #define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09) | |
41 | ||
139b011a PF |
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 | struct ether_addr mac_addr; | |
f12abb48 | 50 | DHCP6IA ia_na; |
a9aff361 | 51 | be32_t transaction_id; |
631bbe71 | 52 | struct sd_dhcp6_lease *lease; |
a9aff361 PF |
53 | int fd; |
54 | sd_event_source *receive_message; | |
d1b0afe3 PF |
55 | usec_t retransmit_time; |
56 | uint8_t retransmit_count; | |
57 | sd_event_source *timeout_resend; | |
58 | sd_event_source *timeout_resend_expire; | |
139b011a PF |
59 | sd_dhcp6_client_cb_t cb; |
60 | void *userdata; | |
a276e6d6 TG |
61 | |
62 | struct duid_en { | |
63 | uint16_t type; /* DHCP6_DUID_EN */ | |
64 | uint32_t pen; | |
65 | uint8_t id[8]; | |
66 | } _packed_ duid; | |
139b011a PF |
67 | }; |
68 | ||
a9aff361 PF |
69 | const char * dhcp6_message_type_table[_DHCP6_MESSAGE_MAX] = { |
70 | [DHCP6_SOLICIT] = "SOLICIT", | |
71 | [DHCP6_ADVERTISE] = "ADVERTISE", | |
72 | [DHCP6_REQUEST] = "REQUEST", | |
73 | [DHCP6_CONFIRM] = "CONFIRM", | |
74 | [DHCP6_RENEW] = "RENEW", | |
75 | [DHCP6_REBIND] = "REBIND", | |
76 | [DHCP6_REPLY] = "REPLY", | |
77 | [DHCP6_RELEASE] = "RELEASE", | |
78 | [DHCP6_DECLINE] = "DECLINE", | |
79 | [DHCP6_RECONFIGURE] = "RECONFIGURE", | |
80 | [DHCP6_INFORMATION_REQUEST] = "INFORMATION-REQUEST", | |
81 | [DHCP6_RELAY_FORW] = "RELAY-FORW", | |
82 | [DHCP6_RELAY_REPL] = "RELAY-REPL", | |
83 | }; | |
84 | ||
85 | DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int); | |
86 | ||
631bbe71 PF |
87 | const char * dhcp6_message_status_table[_DHCP6_STATUS_MAX] = { |
88 | [DHCP6_STATUS_SUCCESS] = "Success", | |
89 | [DHCP6_STATUS_UNSPEC_FAIL] = "Unspecified failure", | |
90 | [DHCP6_STATUS_NO_ADDRS_AVAIL] = "No addresses available", | |
91 | [DHCP6_STATUS_NO_BINDING] = "Binding unavailable", | |
92 | [DHCP6_STATUS_NOT_ON_LINK] = "Not on link", | |
93 | [DHCP6_STATUS_USE_MULTICAST] = "Use multicast", | |
94 | }; | |
95 | ||
96 | DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int); | |
97 | ||
3f0c075f PF |
98 | DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_client*, sd_dhcp6_client_unref); |
99 | #define _cleanup_dhcp6_client_unref_ _cleanup_(sd_dhcp6_client_unrefp) | |
100 | ||
101 | #define DHCP6_CLIENT_DONT_DESTROY(client) \ | |
102 | _cleanup_dhcp6_client_unref_ _unused_ sd_dhcp6_client *_dont_destroy_##client = sd_dhcp6_client_ref(client) | |
103 | ||
c3e2adea PF |
104 | static int client_start(sd_dhcp6_client *client, enum DHCP6State state); |
105 | ||
139b011a PF |
106 | int sd_dhcp6_client_set_callback(sd_dhcp6_client *client, |
107 | sd_dhcp6_client_cb_t cb, void *userdata) | |
108 | { | |
109 | assert_return(client, -EINVAL); | |
110 | ||
111 | client->cb = cb; | |
112 | client->userdata = userdata; | |
113 | ||
114 | return 0; | |
115 | } | |
116 | ||
117 | int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index) | |
118 | { | |
119 | assert_return(client, -EINVAL); | |
120 | assert_return(interface_index >= -1, -EINVAL); | |
121 | ||
122 | client->index = interface_index; | |
123 | ||
124 | return 0; | |
125 | } | |
126 | ||
127 | int sd_dhcp6_client_set_mac(sd_dhcp6_client *client, | |
128 | const struct ether_addr *mac_addr) | |
129 | { | |
130 | assert_return(client, -EINVAL); | |
131 | ||
132 | if (mac_addr) | |
133 | memcpy(&client->mac_addr, mac_addr, sizeof(client->mac_addr)); | |
134 | else | |
135 | memset(&client->mac_addr, 0x00, sizeof(client->mac_addr)); | |
136 | ||
137 | return 0; | |
138 | } | |
139 | ||
ea3b3a75 PF |
140 | int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) { |
141 | assert_return(client, -EINVAL); | |
142 | assert_return(ret, -EINVAL); | |
143 | ||
144 | if (!client->lease) | |
145 | return -ENOMSG; | |
146 | ||
147 | *ret = sd_dhcp6_lease_ref(client->lease); | |
148 | ||
149 | return 0; | |
150 | } | |
151 | ||
3f0c075f PF |
152 | static void client_notify(sd_dhcp6_client *client, int event) { |
153 | if (client->cb) | |
139b011a | 154 | client->cb(client, event, client->userdata); |
139b011a PF |
155 | } |
156 | ||
c806ffb9 | 157 | static int client_reset(sd_dhcp6_client *client) { |
139b011a PF |
158 | assert_return(client, -EINVAL); |
159 | ||
a9aff361 PF |
160 | client->receive_message = |
161 | sd_event_source_unref(client->receive_message); | |
162 | ||
c806ffb9 | 163 | client->fd = safe_close(client->fd); |
a9aff361 | 164 | |
c3e2adea | 165 | client->transaction_id = 0; |
a9aff361 | 166 | |
f12abb48 PF |
167 | client->ia_na.timeout_t1 = |
168 | sd_event_source_unref(client->ia_na.timeout_t1); | |
169 | client->ia_na.timeout_t2 = | |
170 | sd_event_source_unref(client->ia_na.timeout_t2); | |
171 | ||
d1b0afe3 PF |
172 | client->retransmit_time = 0; |
173 | client->retransmit_count = 0; | |
174 | client->timeout_resend = sd_event_source_unref(client->timeout_resend); | |
175 | client->timeout_resend_expire = | |
176 | sd_event_source_unref(client->timeout_resend_expire); | |
177 | ||
139b011a PF |
178 | client->state = DHCP6_STATE_STOPPED; |
179 | ||
180 | return 0; | |
181 | } | |
182 | ||
3f0c075f PF |
183 | static void client_stop(sd_dhcp6_client *client, int error) { |
184 | DHCP6_CLIENT_DONT_DESTROY(client); | |
139b011a | 185 | |
3f0c075f | 186 | assert(client); |
139b011a | 187 | |
3f0c075f PF |
188 | client_notify(client, error); |
189 | ||
190 | client_reset(client); | |
139b011a PF |
191 | } |
192 | ||
a9aff361 PF |
193 | static int client_send_message(sd_dhcp6_client *client) { |
194 | _cleanup_free_ DHCP6Message *message = NULL; | |
195 | struct in6_addr all_servers = | |
196 | IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT; | |
197 | size_t len, optlen = 512; | |
198 | uint8_t *opt; | |
199 | int r; | |
200 | ||
201 | len = sizeof(DHCP6Message) + optlen; | |
202 | ||
203 | message = malloc0(len); | |
204 | if (!message) | |
205 | return -ENOMEM; | |
206 | ||
207 | opt = (uint8_t *)(message + 1); | |
208 | ||
209 | message->transaction_id = client->transaction_id; | |
210 | ||
211 | switch(client->state) { | |
212 | case DHCP6_STATE_SOLICITATION: | |
213 | message->type = DHCP6_SOLICIT; | |
214 | ||
7246333c | 215 | r = dhcp6_option_append_ia(&opt, &optlen, &client->ia_na); |
a9aff361 PF |
216 | if (r < 0) |
217 | return r; | |
218 | ||
7246333c PF |
219 | break; |
220 | ||
221 | case DHCP6_STATE_REQUEST: | |
222 | message->type = DHCP6_REQUEST; | |
223 | ||
224 | r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_SERVERID, | |
225 | client->lease->serverid_len, | |
226 | client->lease->serverid); | |
227 | if (r < 0) | |
228 | return r; | |
229 | ||
230 | r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia); | |
a9aff361 PF |
231 | if (r < 0) |
232 | return r; | |
233 | ||
234 | break; | |
235 | ||
236 | case DHCP6_STATE_STOPPED: | |
237 | case DHCP6_STATE_RS: | |
a34b57c0 | 238 | case DHCP6_STATE_BOUND: |
a9aff361 PF |
239 | return -EINVAL; |
240 | } | |
241 | ||
7246333c PF |
242 | r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_CLIENTID, |
243 | sizeof(client->duid), &client->duid); | |
244 | if (r < 0) | |
245 | return r; | |
246 | ||
a9aff361 PF |
247 | r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message, |
248 | len - optlen); | |
249 | if (r < 0) | |
250 | return r; | |
251 | ||
252 | log_dhcp6_client(client, "Sent %s", | |
253 | dhcp6_message_type_to_string(message->type)); | |
254 | ||
255 | return 0; | |
256 | } | |
257 | ||
a34b57c0 PF |
258 | static int client_timeout_t2(sd_event_source *s, uint64_t usec, |
259 | void *userdata) { | |
260 | sd_dhcp6_client *client = userdata; | |
261 | ||
262 | assert_return(s, -EINVAL); | |
263 | assert_return(client, -EINVAL); | |
264 | assert_return(client->lease, -EINVAL); | |
265 | ||
266 | client->lease->ia.timeout_t2 = | |
267 | sd_event_source_unref(client->lease->ia.timeout_t2); | |
268 | ||
269 | log_dhcp6_client(client, "Timeout T2"); | |
270 | ||
271 | return 0; | |
272 | } | |
273 | ||
274 | static int client_timeout_t1(sd_event_source *s, uint64_t usec, | |
275 | void *userdata) { | |
276 | sd_dhcp6_client *client = userdata; | |
277 | ||
278 | assert_return(s, -EINVAL); | |
279 | assert_return(client, -EINVAL); | |
280 | assert_return(client->lease, -EINVAL); | |
281 | ||
282 | client->lease->ia.timeout_t1 = | |
283 | sd_event_source_unref(client->lease->ia.timeout_t1); | |
284 | ||
285 | log_dhcp6_client(client, "Timeout T1"); | |
286 | ||
287 | return 0; | |
288 | } | |
289 | ||
d1b0afe3 PF |
290 | static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec, |
291 | void *userdata) { | |
292 | sd_dhcp6_client *client = userdata; | |
293 | ||
294 | assert(s); | |
295 | assert(client); | |
296 | assert(client->event); | |
297 | ||
298 | client_stop(client, DHCP6_EVENT_RESEND_EXPIRE); | |
299 | ||
300 | return 0; | |
301 | } | |
302 | ||
303 | static usec_t client_timeout_compute_random(usec_t val) { | |
304 | return val - val / 10 + | |
305 | (random_u32() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC; | |
306 | } | |
307 | ||
308 | static int client_timeout_resend(sd_event_source *s, uint64_t usec, | |
309 | void *userdata) { | |
310 | int r = 0; | |
311 | sd_dhcp6_client *client = userdata; | |
312 | usec_t time_now, init_retransmit_time, max_retransmit_time; | |
313 | usec_t max_retransmit_duration; | |
513a6fa8 | 314 | uint8_t max_retransmit_count = 0; |
d1b0afe3 PF |
315 | char time_string[FORMAT_TIMESPAN_MAX]; |
316 | ||
317 | assert(s); | |
318 | assert(client); | |
319 | assert(client->event); | |
320 | ||
321 | client->timeout_resend = sd_event_source_unref(client->timeout_resend); | |
322 | ||
323 | switch (client->state) { | |
324 | case DHCP6_STATE_SOLICITATION: | |
7246333c PF |
325 | |
326 | if (client->retransmit_count && client->lease) { | |
327 | client_start(client, DHCP6_STATE_REQUEST); | |
328 | return 0; | |
329 | } | |
330 | ||
d1b0afe3 PF |
331 | init_retransmit_time = DHCP6_SOL_TIMEOUT; |
332 | max_retransmit_time = DHCP6_SOL_MAX_RT; | |
333 | max_retransmit_count = 0; | |
334 | max_retransmit_duration = 0; | |
335 | ||
336 | break; | |
337 | ||
7246333c PF |
338 | case DHCP6_STATE_REQUEST: |
339 | init_retransmit_time = DHCP6_REQ_TIMEOUT; | |
340 | max_retransmit_time = DHCP6_REQ_MAX_RT; | |
341 | max_retransmit_count = DHCP6_REQ_MAX_RC; | |
342 | max_retransmit_duration = 0; | |
343 | ||
344 | break; | |
345 | ||
d1b0afe3 PF |
346 | case DHCP6_STATE_STOPPED: |
347 | case DHCP6_STATE_RS: | |
a34b57c0 | 348 | case DHCP6_STATE_BOUND: |
d1b0afe3 PF |
349 | return 0; |
350 | } | |
351 | ||
352 | if (max_retransmit_count && | |
353 | client->retransmit_count >= max_retransmit_count) { | |
354 | client_stop(client, DHCP6_EVENT_RETRANS_MAX); | |
355 | return 0; | |
356 | } | |
357 | ||
a9aff361 PF |
358 | r = client_send_message(client); |
359 | if (r >= 0) | |
360 | client->retransmit_count++; | |
361 | ||
362 | ||
d1b0afe3 PF |
363 | r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now); |
364 | if (r < 0) | |
365 | goto error; | |
366 | ||
367 | if (!client->retransmit_time) { | |
368 | client->retransmit_time = | |
369 | client_timeout_compute_random(init_retransmit_time); | |
a9aff361 PF |
370 | |
371 | if (client->state == DHCP6_STATE_SOLICITATION) | |
372 | client->retransmit_time += init_retransmit_time / 10; | |
373 | ||
d1b0afe3 PF |
374 | } else { |
375 | if (max_retransmit_time && | |
376 | client->retransmit_time > max_retransmit_time / 2) | |
377 | client->retransmit_time = client_timeout_compute_random(max_retransmit_time); | |
378 | else | |
379 | client->retransmit_time += client_timeout_compute_random(client->retransmit_time); | |
380 | } | |
381 | ||
382 | log_dhcp6_client(client, "Next retransmission in %s", | |
383 | format_timespan(time_string, FORMAT_TIMESPAN_MAX, | |
384 | client->retransmit_time, 0)); | |
385 | ||
386 | r = sd_event_add_time(client->event, &client->timeout_resend, | |
387 | CLOCK_MONOTONIC, | |
388 | time_now + client->retransmit_time, | |
389 | 10 * USEC_PER_MSEC, client_timeout_resend, | |
390 | client); | |
391 | if (r < 0) | |
392 | goto error; | |
393 | ||
394 | r = sd_event_source_set_priority(client->timeout_resend, | |
395 | client->event_priority); | |
396 | if (r < 0) | |
397 | goto error; | |
398 | ||
399 | if (max_retransmit_duration && !client->timeout_resend_expire) { | |
400 | ||
401 | log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs", | |
402 | max_retransmit_duration / USEC_PER_SEC); | |
403 | ||
404 | r = sd_event_add_time(client->event, | |
405 | &client->timeout_resend_expire, | |
406 | CLOCK_MONOTONIC, | |
407 | time_now + max_retransmit_duration, | |
408 | USEC_PER_SEC, | |
409 | client_timeout_resend_expire, client); | |
410 | if (r < 0) | |
411 | goto error; | |
412 | ||
413 | r = sd_event_source_set_priority(client->timeout_resend_expire, | |
414 | client->event_priority); | |
415 | if (r < 0) | |
416 | goto error; | |
417 | } | |
418 | ||
419 | error: | |
420 | if (r < 0) | |
421 | client_stop(client, r); | |
422 | ||
423 | return 0; | |
424 | } | |
425 | ||
f12abb48 PF |
426 | static int client_ensure_iaid(sd_dhcp6_client *client) { |
427 | const char *name = NULL; | |
428 | uint64_t id; | |
429 | ||
430 | assert(client); | |
431 | ||
432 | if (client->ia_na.id) | |
433 | return 0; | |
434 | ||
435 | if (detect_container(NULL) <= 0) { | |
436 | /* not in a container, udev will be around */ | |
437 | _cleanup_udev_unref_ struct udev *udev; | |
513a6fa8 | 438 | _cleanup_udev_device_unref_ struct udev_device *device = NULL; |
f12abb48 PF |
439 | char ifindex_str[2 + DECIMAL_STR_MAX(int)]; |
440 | ||
441 | udev = udev_new(); | |
442 | if (!udev) | |
443 | return -ENOMEM; | |
444 | ||
445 | sprintf(ifindex_str, "n%d", client->index); | |
446 | device = udev_device_new_from_device_id(udev, ifindex_str); | |
447 | if (!device) | |
448 | return -errno; | |
449 | ||
450 | if (udev_device_get_is_initialized(device) <= 0) | |
451 | /* not yet ready */ | |
452 | return -EBUSY; | |
453 | ||
454 | name = net_get_name(device); | |
455 | } | |
456 | ||
457 | if (name) | |
458 | siphash24((uint8_t*)&id, name, strlen(name), HASH_KEY.bytes); | |
459 | else | |
460 | /* fall back to mac address if no predictable name available */ | |
461 | siphash24((uint8_t*)&id, &client->mac_addr, ETH_ALEN, | |
462 | HASH_KEY.bytes); | |
463 | ||
464 | /* fold into 32 bits */ | |
465 | client->ia_na.id = (id & 0xffffffff) ^ (id >> 32); | |
466 | ||
467 | return 0; | |
468 | } | |
469 | ||
631bbe71 PF |
470 | static int client_parse_message(sd_dhcp6_client *client, |
471 | DHCP6Message *message, size_t len, | |
472 | sd_dhcp6_lease *lease) { | |
473 | int r; | |
474 | uint8_t *optval, *option = (uint8_t *)(message + 1), *id = NULL; | |
475 | uint16_t optcode, status; | |
476 | size_t optlen, id_len; | |
477 | bool clientid = false; | |
478 | be32_t iaid_lease; | |
479 | ||
480 | while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen, | |
481 | &optval)) >= 0) { | |
482 | switch (optcode) { | |
483 | case DHCP6_OPTION_CLIENTID: | |
484 | if (clientid) { | |
485 | log_dhcp6_client(client, "%s contains multiple clientids", | |
486 | dhcp6_message_type_to_string(message->type)); | |
487 | return -EINVAL; | |
488 | } | |
489 | ||
490 | if (optlen != sizeof(client->duid) || | |
491 | memcmp(&client->duid, optval, optlen) != 0) { | |
492 | log_dhcp6_client(client, "%s DUID does not match", | |
493 | dhcp6_message_type_to_string(message->type)); | |
494 | ||
495 | return -EINVAL; | |
496 | } | |
497 | clientid = true; | |
498 | ||
499 | break; | |
500 | ||
501 | case DHCP6_OPTION_SERVERID: | |
502 | r = dhcp6_lease_get_serverid(lease, &id, &id_len); | |
503 | if (r >= 0 && id) { | |
504 | log_dhcp6_client(client, "%s contains multiple serverids", | |
505 | dhcp6_message_type_to_string(message->type)); | |
506 | return -EINVAL; | |
507 | } | |
508 | ||
509 | r = dhcp6_lease_set_serverid(lease, optval, optlen); | |
510 | if (r < 0) | |
511 | return r; | |
512 | ||
513 | break; | |
514 | ||
515 | case DHCP6_OPTION_PREFERENCE: | |
516 | if (optlen != 1) | |
517 | return -EINVAL; | |
518 | ||
519 | r = dhcp6_lease_set_preference(lease, *optval); | |
520 | if (r < 0) | |
521 | return r; | |
522 | ||
523 | break; | |
524 | ||
525 | case DHCP6_OPTION_STATUS_CODE: | |
526 | if (optlen < 2) | |
527 | return -EINVAL; | |
528 | ||
529 | status = optval[0] << 8 | optval[1]; | |
530 | if (status) { | |
531 | log_dhcp6_client(client, "%s Status %s", | |
532 | dhcp6_message_type_to_string(message->type), | |
533 | dhcp6_message_status_to_string(status)); | |
534 | return -EINVAL; | |
535 | } | |
536 | ||
537 | break; | |
538 | ||
539 | case DHCP6_OPTION_IA_NA: | |
540 | r = dhcp6_option_parse_ia(&optval, &optlen, optcode, | |
541 | &lease->ia); | |
542 | if (r < 0 && r != -ENOMSG) | |
543 | return r; | |
544 | ||
545 | r = dhcp6_lease_get_iaid(lease, &iaid_lease); | |
546 | if (r < 0) | |
547 | return r; | |
548 | ||
549 | if (client->ia_na.id != iaid_lease) { | |
550 | log_dhcp6_client(client, "%s has wrong IAID", | |
551 | dhcp6_message_type_to_string(message->type)); | |
552 | return -EINVAL; | |
553 | } | |
554 | ||
555 | break; | |
556 | } | |
557 | } | |
558 | ||
559 | if ((r < 0 && r != -ENOMSG) || !clientid) { | |
560 | log_dhcp6_client(client, "%s has incomplete options", | |
561 | dhcp6_message_type_to_string(message->type)); | |
562 | return -EINVAL; | |
563 | } | |
564 | ||
565 | r = dhcp6_lease_get_serverid(lease, &id, &id_len); | |
566 | if (r < 0) | |
567 | log_dhcp6_client(client, "%s has no server id", | |
568 | dhcp6_message_type_to_string(message->type)); | |
569 | ||
570 | return r; | |
571 | } | |
572 | ||
a34b57c0 PF |
573 | static int client_receive_reply(sd_dhcp6_client *client, DHCP6Message *reply, |
574 | size_t len) | |
575 | { | |
576 | int r; | |
577 | _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL; | |
578 | ||
579 | if (reply->type != DHCP6_REPLY) | |
580 | return -EINVAL; | |
581 | ||
582 | r = dhcp6_lease_new(&lease); | |
583 | if (r < 0) | |
584 | return -ENOMEM; | |
585 | ||
586 | r = client_parse_message(client, reply, len, lease); | |
587 | if (r < 0) | |
588 | return r; | |
589 | ||
590 | dhcp6_lease_clear_timers(&client->lease->ia); | |
591 | ||
592 | client->lease = sd_dhcp6_lease_unref(client->lease); | |
593 | client->lease = lease; | |
594 | lease = NULL; | |
595 | ||
596 | return DHCP6_STATE_BOUND; | |
597 | } | |
598 | ||
631bbe71 PF |
599 | static int client_receive_advertise(sd_dhcp6_client *client, |
600 | DHCP6Message *advertise, size_t len) { | |
601 | int r; | |
602 | _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL; | |
603 | uint8_t pref_advertise = 0, pref_lease = 0; | |
604 | ||
605 | if (advertise->type != DHCP6_ADVERTISE) | |
606 | return -EINVAL; | |
607 | ||
608 | r = dhcp6_lease_new(&lease); | |
609 | if (r < 0) | |
610 | return r; | |
611 | ||
612 | r = client_parse_message(client, advertise, len, lease); | |
613 | if (r < 0) | |
614 | return r; | |
615 | ||
616 | r = dhcp6_lease_get_preference(lease, &pref_advertise); | |
617 | if (r < 0) | |
618 | return r; | |
619 | ||
620 | r = dhcp6_lease_get_preference(client->lease, &pref_lease); | |
621 | if (!client->lease || r < 0 || pref_advertise > pref_lease) { | |
622 | sd_dhcp6_lease_unref(client->lease); | |
623 | client->lease = lease; | |
624 | lease = NULL; | |
625 | r = 0; | |
626 | } | |
627 | ||
7246333c PF |
628 | if (pref_advertise == 255 || client->retransmit_count > 1) |
629 | r = DHCP6_STATE_REQUEST; | |
630 | ||
631bbe71 PF |
631 | return r; |
632 | } | |
633 | ||
a9aff361 | 634 | static int client_receive_message(sd_event_source *s, int fd, uint32_t revents, |
631bbe71 PF |
635 | void *userdata) { |
636 | sd_dhcp6_client *client = userdata; | |
3f0c075f | 637 | DHCP6_CLIENT_DONT_DESTROY(client); |
631bbe71 PF |
638 | _cleanup_free_ DHCP6Message *message; |
639 | int r, buflen, len; | |
640 | ||
641 | assert(s); | |
642 | assert(client); | |
643 | assert(client->event); | |
644 | ||
645 | r = ioctl(fd, FIONREAD, &buflen); | |
646 | if (r < 0 || buflen <= 0) | |
647 | buflen = DHCP6_MIN_OPTIONS_SIZE; | |
648 | ||
649 | message = malloc0(buflen); | |
650 | if (!message) | |
651 | return -ENOMEM; | |
652 | ||
653 | len = read(fd, message, buflen); | |
654 | if ((size_t)len < sizeof(DHCP6Message)) { | |
6ec60d20 | 655 | log_dhcp6_client(client, "could not receive message from UDP socket: %m"); |
631bbe71 PF |
656 | return 0; |
657 | } | |
658 | ||
659 | switch(message->type) { | |
660 | case DHCP6_SOLICIT: | |
661 | case DHCP6_REQUEST: | |
662 | case DHCP6_CONFIRM: | |
663 | case DHCP6_RENEW: | |
664 | case DHCP6_REBIND: | |
665 | case DHCP6_RELEASE: | |
666 | case DHCP6_DECLINE: | |
667 | case DHCP6_INFORMATION_REQUEST: | |
668 | case DHCP6_RELAY_FORW: | |
669 | case DHCP6_RELAY_REPL: | |
670 | return 0; | |
671 | ||
672 | case DHCP6_ADVERTISE: | |
673 | case DHCP6_REPLY: | |
674 | case DHCP6_RECONFIGURE: | |
675 | break; | |
676 | ||
677 | default: | |
678 | log_dhcp6_client(client, "unknown message type %d", | |
679 | message->type); | |
680 | return 0; | |
681 | } | |
682 | ||
683 | if (client->transaction_id != (message->transaction_id & | |
684 | htobe32(0x00ffffff))) | |
685 | return 0; | |
686 | ||
687 | switch (client->state) { | |
688 | case DHCP6_STATE_SOLICITATION: | |
689 | r = client_receive_advertise(client, message, len); | |
690 | ||
7246333c PF |
691 | if (r == DHCP6_STATE_REQUEST) |
692 | client_start(client, r); | |
693 | ||
631bbe71 PF |
694 | break; |
695 | ||
7246333c | 696 | case DHCP6_STATE_REQUEST: |
a34b57c0 PF |
697 | r = client_receive_reply(client, message, len); |
698 | if (r < 0) | |
699 | return 0; | |
700 | ||
701 | if (r == DHCP6_STATE_BOUND) { | |
702 | ||
703 | r = client_start(client, DHCP6_STATE_BOUND); | |
704 | if (r < 0) { | |
705 | client_stop(client, r); | |
706 | return 0; | |
707 | } | |
708 | ||
3f0c075f | 709 | client_notify(client, DHCP6_EVENT_IP_ACQUIRE); |
a34b57c0 PF |
710 | } |
711 | ||
712 | break; | |
713 | ||
714 | case DHCP6_STATE_BOUND: | |
715 | ||
716 | break; | |
717 | ||
631bbe71 PF |
718 | case DHCP6_STATE_STOPPED: |
719 | case DHCP6_STATE_RS: | |
720 | return 0; | |
721 | } | |
722 | ||
723 | if (r >= 0) { | |
724 | log_dhcp6_client(client, "Recv %s", | |
725 | dhcp6_message_type_to_string(message->type)); | |
726 | } | |
727 | ||
a9aff361 PF |
728 | return 0; |
729 | } | |
730 | ||
c3e2adea | 731 | static int client_start(sd_dhcp6_client *client, enum DHCP6State state) |
f12abb48 PF |
732 | { |
733 | int r; | |
a34b57c0 PF |
734 | usec_t timeout, time_now; |
735 | char time_string[FORMAT_TIMESPAN_MAX]; | |
f12abb48 PF |
736 | |
737 | assert_return(client, -EINVAL); | |
738 | assert_return(client->event, -EINVAL); | |
739 | assert_return(client->index > 0, -EINVAL); | |
c3e2adea | 740 | assert_return(client->state != state, -EINVAL); |
f12abb48 | 741 | |
c3e2adea PF |
742 | client->timeout_resend_expire = |
743 | sd_event_source_unref(client->timeout_resend_expire); | |
744 | client->timeout_resend = sd_event_source_unref(client->timeout_resend); | |
745 | client->retransmit_time = 0; | |
746 | client->retransmit_count = 0; | |
f12abb48 | 747 | |
c3e2adea PF |
748 | switch (state) { |
749 | case DHCP6_STATE_STOPPED: | |
750 | case DHCP6_STATE_RS: | |
751 | case DHCP6_STATE_SOLICITATION: | |
a9aff361 | 752 | |
c3e2adea PF |
753 | r = client_ensure_iaid(client); |
754 | if (r < 0) | |
755 | return r; | |
a9aff361 | 756 | |
c3e2adea PF |
757 | r = dhcp6_network_bind_udp_socket(client->index, NULL); |
758 | if (r < 0) | |
759 | return r; | |
a9aff361 | 760 | |
c3e2adea PF |
761 | client->fd = r; |
762 | ||
763 | r = sd_event_add_io(client->event, &client->receive_message, | |
764 | client->fd, EPOLLIN, client_receive_message, | |
765 | client); | |
766 | if (r < 0) | |
767 | return r; | |
768 | ||
769 | r = sd_event_source_set_priority(client->receive_message, | |
770 | client->event_priority); | |
771 | if (r < 0) | |
772 | return r; | |
773 | ||
774 | client->state = DHCP6_STATE_SOLICITATION; | |
775 | ||
7246333c PF |
776 | break; |
777 | ||
778 | case DHCP6_STATE_REQUEST: | |
a34b57c0 | 779 | |
7246333c PF |
780 | client->state = state; |
781 | ||
c3e2adea | 782 | break; |
a34b57c0 PF |
783 | |
784 | case DHCP6_STATE_BOUND: | |
785 | ||
786 | r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now); | |
787 | if (r < 0) | |
788 | return r; | |
789 | ||
790 | if (client->lease->ia.lifetime_t1 == 0xffffffff || | |
791 | client->lease->ia.lifetime_t2 == 0xffffffff) { | |
792 | ||
793 | log_dhcp6_client(client, "infinite T1 0x%08x or T2 0x%08x", | |
794 | be32toh(client->lease->ia.lifetime_t1), | |
795 | be32toh(client->lease->ia.lifetime_t2)); | |
796 | ||
797 | return 0; | |
798 | } | |
799 | ||
800 | timeout = client_timeout_compute_random(be32toh(client->lease->ia.lifetime_t1) * USEC_PER_SEC); | |
801 | ||
802 | log_dhcp6_client(client, "T1 expires in %s", | |
803 | format_timespan(time_string, | |
804 | FORMAT_TIMESPAN_MAX, | |
805 | timeout, 0)); | |
806 | ||
807 | r = sd_event_add_time(client->event, | |
808 | &client->lease->ia.timeout_t1, | |
809 | CLOCK_MONOTONIC, time_now + timeout, | |
810 | 10 * USEC_PER_SEC, client_timeout_t1, | |
811 | client); | |
812 | if (r < 0) | |
813 | return r; | |
814 | ||
815 | r = sd_event_source_set_priority(client->lease->ia.timeout_t1, | |
816 | client->event_priority); | |
817 | if (r < 0) | |
818 | return r; | |
819 | ||
820 | timeout = client_timeout_compute_random(be32toh(client->lease->ia.lifetime_t2) * USEC_PER_SEC); | |
821 | ||
822 | log_dhcp6_client(client, "T2 expires in %s", | |
823 | format_timespan(time_string, | |
824 | FORMAT_TIMESPAN_MAX, | |
825 | timeout, 0)); | |
826 | ||
827 | r = sd_event_add_time(client->event, | |
828 | &client->lease->ia.timeout_t2, | |
829 | CLOCK_MONOTONIC, time_now + timeout, | |
830 | 10 * USEC_PER_SEC, client_timeout_t2, | |
831 | client); | |
832 | if (r < 0) | |
833 | return r; | |
834 | ||
835 | r = sd_event_source_set_priority(client->lease->ia.timeout_t2, | |
836 | client->event_priority); | |
837 | if (r < 0) | |
838 | return r; | |
839 | ||
840 | return 0; | |
c3e2adea | 841 | } |
a9aff361 | 842 | |
c3e2adea | 843 | client->transaction_id = random_u32() & htobe32(0x00ffffff); |
d1b0afe3 PF |
844 | |
845 | r = sd_event_add_time(client->event, &client->timeout_resend, | |
846 | CLOCK_MONOTONIC, 0, 0, client_timeout_resend, | |
847 | client); | |
848 | if (r < 0) | |
849 | return r; | |
850 | ||
851 | r = sd_event_source_set_priority(client->timeout_resend, | |
852 | client->event_priority); | |
853 | if (r < 0) | |
854 | return r; | |
855 | ||
f12abb48 PF |
856 | return 0; |
857 | } | |
858 | ||
139b011a PF |
859 | int sd_dhcp6_client_stop(sd_dhcp6_client *client) |
860 | { | |
861 | client_stop(client, DHCP6_EVENT_STOP); | |
862 | ||
863 | return 0; | |
864 | } | |
865 | ||
866 | int sd_dhcp6_client_start(sd_dhcp6_client *client) | |
867 | { | |
868 | int r = 0; | |
869 | ||
870 | assert_return(client, -EINVAL); | |
871 | assert_return(client->event, -EINVAL); | |
872 | assert_return(client->index > 0, -EINVAL); | |
873 | ||
c806ffb9 | 874 | r = client_reset(client); |
f12abb48 PF |
875 | if (r < 0) |
876 | return r; | |
877 | ||
c3e2adea | 878 | return client_start(client, DHCP6_STATE_SOLICITATION); |
139b011a PF |
879 | } |
880 | ||
881 | int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, | |
882 | int priority) | |
883 | { | |
884 | int r; | |
885 | ||
886 | assert_return(client, -EINVAL); | |
887 | assert_return(!client->event, -EBUSY); | |
888 | ||
889 | if (event) | |
890 | client->event = sd_event_ref(event); | |
891 | else { | |
892 | r = sd_event_default(&client->event); | |
893 | if (r < 0) | |
894 | return 0; | |
895 | } | |
896 | ||
897 | client->event_priority = priority; | |
898 | ||
899 | return 0; | |
900 | } | |
901 | ||
902 | int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) { | |
903 | assert_return(client, -EINVAL); | |
904 | ||
905 | client->event = sd_event_unref(client->event); | |
906 | ||
907 | return 0; | |
908 | } | |
909 | ||
910 | sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) { | |
911 | if (!client) | |
912 | return NULL; | |
913 | ||
914 | return client->event; | |
915 | } | |
916 | ||
917 | sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client) { | |
918 | if (client) | |
919 | assert_se(REFCNT_INC(client->n_ref) >= 2); | |
920 | ||
921 | return client; | |
922 | } | |
923 | ||
924 | sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) { | |
925 | if (client && REFCNT_DEC(client->n_ref) <= 0) { | |
c806ffb9 | 926 | client_reset(client); |
139b011a PF |
927 | |
928 | sd_dhcp6_client_detach_event(client); | |
929 | ||
930 | free(client); | |
931 | ||
932 | return NULL; | |
933 | } | |
934 | ||
935 | return client; | |
936 | } | |
937 | ||
139b011a PF |
938 | int sd_dhcp6_client_new(sd_dhcp6_client **ret) |
939 | { | |
3f0c075f | 940 | _cleanup_dhcp6_client_unref_ sd_dhcp6_client *client = NULL; |
a276e6d6 TG |
941 | sd_id128_t machine_id; |
942 | int r; | |
139b011a PF |
943 | |
944 | assert_return(ret, -EINVAL); | |
945 | ||
946 | client = new0(sd_dhcp6_client, 1); | |
947 | if (!client) | |
948 | return -ENOMEM; | |
949 | ||
950 | client->n_ref = REFCNT_INIT; | |
951 | ||
f12abb48 PF |
952 | client->ia_na.type = DHCP6_OPTION_IA_NA; |
953 | ||
139b011a PF |
954 | client->index = -1; |
955 | ||
c806ffb9 ZJS |
956 | client->fd = -1; |
957 | ||
a276e6d6 TG |
958 | /* initialize DUID */ |
959 | client->duid.type = htobe16(DHCP6_DUID_EN); | |
960 | client->duid.pen = htobe32(SYSTEMD_PEN); | |
961 | ||
962 | r = sd_id128_get_machine(&machine_id); | |
963 | if (r < 0) | |
964 | return r; | |
965 | ||
966 | /* a bit of snake-oil perhaps, but no need to expose the machine-id | |
967 | directly */ | |
968 | siphash24(client->duid.id, &machine_id, sizeof(machine_id), | |
969 | HASH_KEY.bytes); | |
970 | ||
139b011a PF |
971 | *ret = client; |
972 | client = NULL; | |
973 | ||
974 | return 0; | |
975 | } |