1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright (C) 2014 Intel Corporation. All rights reserved.
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.
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.
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/>.
26 #include "udev-util.h"
28 #include "siphash24.h"
32 #include "network-internal.h"
33 #include "sd-dhcp6-client.h"
34 #include "dhcp6-protocol.h"
35 #include "dhcp6-internal.h"
37 #define SYSTEMD_PEN 43793
38 #define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09)
40 struct sd_dhcp6_client {
43 enum DHCP6State state;
47 struct ether_addr mac_addr;
49 usec_t retransmit_time;
50 uint8_t retransmit_count;
51 sd_event_source *timeout_resend;
52 sd_event_source *timeout_resend_expire;
53 sd_dhcp6_client_cb_t cb;
57 uint16_t type; /* DHCP6_DUID_EN */
63 int sd_dhcp6_client_set_callback(sd_dhcp6_client *client,
64 sd_dhcp6_client_cb_t cb, void *userdata)
66 assert_return(client, -EINVAL);
69 client->userdata = userdata;
74 int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index)
76 assert_return(client, -EINVAL);
77 assert_return(interface_index >= -1, -EINVAL);
79 client->index = interface_index;
84 int sd_dhcp6_client_set_mac(sd_dhcp6_client *client,
85 const struct ether_addr *mac_addr)
87 assert_return(client, -EINVAL);
90 memcpy(&client->mac_addr, mac_addr, sizeof(client->mac_addr));
92 memset(&client->mac_addr, 0x00, sizeof(client->mac_addr));
97 static sd_dhcp6_client *client_notify(sd_dhcp6_client *client, int event) {
99 client = sd_dhcp6_client_ref(client);
100 client->cb(client, event, client->userdata);
101 client = sd_dhcp6_client_unref(client);
107 static int client_initialize(sd_dhcp6_client *client)
109 assert_return(client, -EINVAL);
111 client->ia_na.timeout_t1 =
112 sd_event_source_unref(client->ia_na.timeout_t1);
113 client->ia_na.timeout_t2 =
114 sd_event_source_unref(client->ia_na.timeout_t2);
116 client->retransmit_time = 0;
117 client->retransmit_count = 0;
118 client->timeout_resend = sd_event_source_unref(client->timeout_resend);
119 client->timeout_resend_expire =
120 sd_event_source_unref(client->timeout_resend_expire);
122 client->state = DHCP6_STATE_STOPPED;
127 static sd_dhcp6_client *client_stop(sd_dhcp6_client *client, int error) {
128 assert_return(client, NULL);
130 client = client_notify(client, error);
132 client_initialize(client);
137 static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec,
139 sd_dhcp6_client *client = userdata;
143 assert(client->event);
145 client_stop(client, DHCP6_EVENT_RESEND_EXPIRE);
150 static usec_t client_timeout_compute_random(usec_t val) {
151 return val - val / 10 +
152 (random_u32() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
155 static int client_timeout_resend(sd_event_source *s, uint64_t usec,
158 sd_dhcp6_client *client = userdata;
159 usec_t time_now, init_retransmit_time, max_retransmit_time;
160 usec_t max_retransmit_duration;
161 uint8_t max_retransmit_count;
162 char time_string[FORMAT_TIMESPAN_MAX];
166 assert(client->event);
168 client->timeout_resend = sd_event_source_unref(client->timeout_resend);
170 switch (client->state) {
171 case DHCP6_STATE_SOLICITATION:
172 init_retransmit_time = DHCP6_SOL_TIMEOUT;
173 max_retransmit_time = DHCP6_SOL_MAX_RT;
174 max_retransmit_count = 0;
175 max_retransmit_duration = 0;
179 case DHCP6_STATE_STOPPED:
184 if (max_retransmit_count &&
185 client->retransmit_count >= max_retransmit_count) {
186 client_stop(client, DHCP6_EVENT_RETRANS_MAX);
190 r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now);
194 if (!client->retransmit_time) {
195 client->retransmit_time =
196 client_timeout_compute_random(init_retransmit_time);
198 if (max_retransmit_time &&
199 client->retransmit_time > max_retransmit_time / 2)
200 client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
202 client->retransmit_time += client_timeout_compute_random(client->retransmit_time);
205 log_dhcp6_client(client, "Next retransmission in %s",
206 format_timespan(time_string, FORMAT_TIMESPAN_MAX,
207 client->retransmit_time, 0));
209 r = sd_event_add_time(client->event, &client->timeout_resend,
211 time_now + client->retransmit_time,
212 10 * USEC_PER_MSEC, client_timeout_resend,
217 r = sd_event_source_set_priority(client->timeout_resend,
218 client->event_priority);
222 if (max_retransmit_duration && !client->timeout_resend_expire) {
224 log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs",
225 max_retransmit_duration / USEC_PER_SEC);
227 r = sd_event_add_time(client->event,
228 &client->timeout_resend_expire,
230 time_now + max_retransmit_duration,
232 client_timeout_resend_expire, client);
236 r = sd_event_source_set_priority(client->timeout_resend_expire,
237 client->event_priority);
244 client_stop(client, r);
249 static int client_ensure_iaid(sd_dhcp6_client *client) {
250 const char *name = NULL;
255 if (client->ia_na.id)
258 if (detect_container(NULL) <= 0) {
259 /* not in a container, udev will be around */
260 _cleanup_udev_unref_ struct udev *udev;
261 _cleanup_udev_device_unref_ struct udev_device *device;
262 char ifindex_str[2 + DECIMAL_STR_MAX(int)];
268 sprintf(ifindex_str, "n%d", client->index);
269 device = udev_device_new_from_device_id(udev, ifindex_str);
273 if (udev_device_get_is_initialized(device) <= 0)
277 name = net_get_name(device);
281 siphash24((uint8_t*)&id, name, strlen(name), HASH_KEY.bytes);
283 /* fall back to mac address if no predictable name available */
284 siphash24((uint8_t*)&id, &client->mac_addr, ETH_ALEN,
287 /* fold into 32 bits */
288 client->ia_na.id = (id & 0xffffffff) ^ (id >> 32);
293 static int client_start(sd_dhcp6_client *client)
297 assert_return(client, -EINVAL);
298 assert_return(client->event, -EINVAL);
299 assert_return(client->index > 0, -EINVAL);
301 r = client_ensure_iaid(client);
305 client->state = DHCP6_STATE_SOLICITATION;
307 r = sd_event_add_time(client->event, &client->timeout_resend,
308 CLOCK_MONOTONIC, 0, 0, client_timeout_resend,
313 r = sd_event_source_set_priority(client->timeout_resend,
314 client->event_priority);
321 int sd_dhcp6_client_stop(sd_dhcp6_client *client)
323 client_stop(client, DHCP6_EVENT_STOP);
328 int sd_dhcp6_client_start(sd_dhcp6_client *client)
332 assert_return(client, -EINVAL);
333 assert_return(client->event, -EINVAL);
334 assert_return(client->index > 0, -EINVAL);
336 r = client_initialize(client);
340 return client_start(client);
343 int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event,
348 assert_return(client, -EINVAL);
349 assert_return(!client->event, -EBUSY);
352 client->event = sd_event_ref(event);
354 r = sd_event_default(&client->event);
359 client->event_priority = priority;
364 int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) {
365 assert_return(client, -EINVAL);
367 client->event = sd_event_unref(client->event);
372 sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
376 return client->event;
379 sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client) {
381 assert_se(REFCNT_INC(client->n_ref) >= 2);
386 sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
387 if (client && REFCNT_DEC(client->n_ref) <= 0) {
388 client_initialize(client);
390 sd_dhcp6_client_detach_event(client);
400 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_client*, sd_dhcp6_client_unref);
401 #define _cleanup_dhcp6_client_free_ _cleanup_(sd_dhcp6_client_unrefp)
403 int sd_dhcp6_client_new(sd_dhcp6_client **ret)
405 _cleanup_dhcp6_client_free_ sd_dhcp6_client *client = NULL;
406 sd_id128_t machine_id;
409 assert_return(ret, -EINVAL);
411 client = new0(sd_dhcp6_client, 1);
415 client->n_ref = REFCNT_INIT;
417 client->ia_na.type = DHCP6_OPTION_IA_NA;
421 /* initialize DUID */
422 client->duid.type = htobe16(DHCP6_DUID_EN);
423 client->duid.pen = htobe32(SYSTEMD_PEN);
425 r = sd_id128_get_machine(&machine_id);
429 /* a bit of snake-oil perhaps, but no need to expose the machine-id
431 siphash24(client->duid.id, &machine_id, sizeof(machine_id),