]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/sd-dhcp6-client.c
sd-dhcp6-client: Add functions to bind to DHCPv6 UDP socket
[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>
24
f12abb48
PF
25#include "udev.h"
26#include "udev-util.h"
27#include "virt.h"
a276e6d6 28#include "siphash24.h"
139b011a
PF
29#include "util.h"
30#include "refcnt.h"
31
f12abb48 32#include "network-internal.h"
139b011a
PF
33#include "sd-dhcp6-client.h"
34#include "dhcp6-protocol.h"
f12abb48 35#include "dhcp6-internal.h"
139b011a 36
a276e6d6
TG
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)
39
139b011a
PF
40struct sd_dhcp6_client {
41 RefCount n_ref;
42
43 enum DHCP6State state;
44 sd_event *event;
45 int event_priority;
46 int index;
47 struct ether_addr mac_addr;
f12abb48 48 DHCP6IA ia_na;
d1b0afe3
PF
49 usec_t retransmit_time;
50 uint8_t retransmit_count;
51 sd_event_source *timeout_resend;
52 sd_event_source *timeout_resend_expire;
139b011a
PF
53 sd_dhcp6_client_cb_t cb;
54 void *userdata;
a276e6d6
TG
55
56 struct duid_en {
57 uint16_t type; /* DHCP6_DUID_EN */
58 uint32_t pen;
59 uint8_t id[8];
60 } _packed_ duid;
139b011a
PF
61};
62
63int sd_dhcp6_client_set_callback(sd_dhcp6_client *client,
64 sd_dhcp6_client_cb_t cb, void *userdata)
65{
66 assert_return(client, -EINVAL);
67
68 client->cb = cb;
69 client->userdata = userdata;
70
71 return 0;
72}
73
74int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index)
75{
76 assert_return(client, -EINVAL);
77 assert_return(interface_index >= -1, -EINVAL);
78
79 client->index = interface_index;
80
81 return 0;
82}
83
84int sd_dhcp6_client_set_mac(sd_dhcp6_client *client,
85 const struct ether_addr *mac_addr)
86{
87 assert_return(client, -EINVAL);
88
89 if (mac_addr)
90 memcpy(&client->mac_addr, mac_addr, sizeof(client->mac_addr));
91 else
92 memset(&client->mac_addr, 0x00, sizeof(client->mac_addr));
93
94 return 0;
95}
96
97static sd_dhcp6_client *client_notify(sd_dhcp6_client *client, int event) {
98 if (client->cb) {
99 client = sd_dhcp6_client_ref(client);
100 client->cb(client, event, client->userdata);
101 client = sd_dhcp6_client_unref(client);
102 }
103
104 return client;
105}
106
107static int client_initialize(sd_dhcp6_client *client)
108{
109 assert_return(client, -EINVAL);
110
f12abb48
PF
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);
115
d1b0afe3
PF
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);
121
139b011a
PF
122 client->state = DHCP6_STATE_STOPPED;
123
124 return 0;
125}
126
127static sd_dhcp6_client *client_stop(sd_dhcp6_client *client, int error) {
128 assert_return(client, NULL);
129
130 client = client_notify(client, error);
131 if (client)
132 client_initialize(client);
133
134 return client;
135}
136
d1b0afe3
PF
137static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec,
138 void *userdata) {
139 sd_dhcp6_client *client = userdata;
140
141 assert(s);
142 assert(client);
143 assert(client->event);
144
145 client_stop(client, DHCP6_EVENT_RESEND_EXPIRE);
146
147 return 0;
148}
149
150static 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;
153}
154
155static int client_timeout_resend(sd_event_source *s, uint64_t usec,
156 void *userdata) {
157 int r = 0;
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];
163
164 assert(s);
165 assert(client);
166 assert(client->event);
167
168 client->timeout_resend = sd_event_source_unref(client->timeout_resend);
169
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;
176
177 break;
178
179 case DHCP6_STATE_STOPPED:
180 case DHCP6_STATE_RS:
181 return 0;
182 }
183
184 if (max_retransmit_count &&
185 client->retransmit_count >= max_retransmit_count) {
186 client_stop(client, DHCP6_EVENT_RETRANS_MAX);
187 return 0;
188 }
189
190 r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now);
191 if (r < 0)
192 goto error;
193
194 if (!client->retransmit_time) {
195 client->retransmit_time =
196 client_timeout_compute_random(init_retransmit_time);
197 } else {
198 if (max_retransmit_time &&
199 client->retransmit_time > max_retransmit_time / 2)
200 client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
201 else
202 client->retransmit_time += client_timeout_compute_random(client->retransmit_time);
203 }
204
205 log_dhcp6_client(client, "Next retransmission in %s",
206 format_timespan(time_string, FORMAT_TIMESPAN_MAX,
207 client->retransmit_time, 0));
208
209 r = sd_event_add_time(client->event, &client->timeout_resend,
210 CLOCK_MONOTONIC,
211 time_now + client->retransmit_time,
212 10 * USEC_PER_MSEC, client_timeout_resend,
213 client);
214 if (r < 0)
215 goto error;
216
217 r = sd_event_source_set_priority(client->timeout_resend,
218 client->event_priority);
219 if (r < 0)
220 goto error;
221
222 if (max_retransmit_duration && !client->timeout_resend_expire) {
223
224 log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs",
225 max_retransmit_duration / USEC_PER_SEC);
226
227 r = sd_event_add_time(client->event,
228 &client->timeout_resend_expire,
229 CLOCK_MONOTONIC,
230 time_now + max_retransmit_duration,
231 USEC_PER_SEC,
232 client_timeout_resend_expire, client);
233 if (r < 0)
234 goto error;
235
236 r = sd_event_source_set_priority(client->timeout_resend_expire,
237 client->event_priority);
238 if (r < 0)
239 goto error;
240 }
241
242error:
243 if (r < 0)
244 client_stop(client, r);
245
246 return 0;
247}
248
f12abb48
PF
249static int client_ensure_iaid(sd_dhcp6_client *client) {
250 const char *name = NULL;
251 uint64_t id;
252
253 assert(client);
254
255 if (client->ia_na.id)
256 return 0;
257
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)];
263
264 udev = udev_new();
265 if (!udev)
266 return -ENOMEM;
267
268 sprintf(ifindex_str, "n%d", client->index);
269 device = udev_device_new_from_device_id(udev, ifindex_str);
270 if (!device)
271 return -errno;
272
273 if (udev_device_get_is_initialized(device) <= 0)
274 /* not yet ready */
275 return -EBUSY;
276
277 name = net_get_name(device);
278 }
279
280 if (name)
281 siphash24((uint8_t*)&id, name, strlen(name), HASH_KEY.bytes);
282 else
283 /* fall back to mac address if no predictable name available */
284 siphash24((uint8_t*)&id, &client->mac_addr, ETH_ALEN,
285 HASH_KEY.bytes);
286
287 /* fold into 32 bits */
288 client->ia_na.id = (id & 0xffffffff) ^ (id >> 32);
289
290 return 0;
291}
292
293static int client_start(sd_dhcp6_client *client)
294{
295 int r;
296
297 assert_return(client, -EINVAL);
298 assert_return(client->event, -EINVAL);
299 assert_return(client->index > 0, -EINVAL);
300
301 r = client_ensure_iaid(client);
302 if (r < 0)
303 return r;
304
d1b0afe3
PF
305 client->state = DHCP6_STATE_SOLICITATION;
306
307 r = sd_event_add_time(client->event, &client->timeout_resend,
308 CLOCK_MONOTONIC, 0, 0, client_timeout_resend,
309 client);
310 if (r < 0)
311 return r;
312
313 r = sd_event_source_set_priority(client->timeout_resend,
314 client->event_priority);
315 if (r < 0)
316 return r;
317
f12abb48
PF
318 return 0;
319}
320
139b011a
PF
321int sd_dhcp6_client_stop(sd_dhcp6_client *client)
322{
323 client_stop(client, DHCP6_EVENT_STOP);
324
325 return 0;
326}
327
328int sd_dhcp6_client_start(sd_dhcp6_client *client)
329{
330 int r = 0;
331
332 assert_return(client, -EINVAL);
333 assert_return(client->event, -EINVAL);
334 assert_return(client->index > 0, -EINVAL);
335
f12abb48
PF
336 r = client_initialize(client);
337 if (r < 0)
338 return r;
339
340 return client_start(client);
139b011a
PF
341}
342
343int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event,
344 int priority)
345{
346 int r;
347
348 assert_return(client, -EINVAL);
349 assert_return(!client->event, -EBUSY);
350
351 if (event)
352 client->event = sd_event_ref(event);
353 else {
354 r = sd_event_default(&client->event);
355 if (r < 0)
356 return 0;
357 }
358
359 client->event_priority = priority;
360
361 return 0;
362}
363
364int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) {
365 assert_return(client, -EINVAL);
366
367 client->event = sd_event_unref(client->event);
368
369 return 0;
370}
371
372sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
373 if (!client)
374 return NULL;
375
376 return client->event;
377}
378
379sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client) {
380 if (client)
381 assert_se(REFCNT_INC(client->n_ref) >= 2);
382
383 return client;
384}
385
386sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
387 if (client && REFCNT_DEC(client->n_ref) <= 0) {
139b011a
PF
388 client_initialize(client);
389
390 sd_dhcp6_client_detach_event(client);
391
392 free(client);
393
394 return NULL;
395 }
396
397 return client;
398}
399
400DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_client*, sd_dhcp6_client_unref);
401#define _cleanup_dhcp6_client_free_ _cleanup_(sd_dhcp6_client_unrefp)
402
403int sd_dhcp6_client_new(sd_dhcp6_client **ret)
404{
405 _cleanup_dhcp6_client_free_ sd_dhcp6_client *client = NULL;
a276e6d6
TG
406 sd_id128_t machine_id;
407 int r;
139b011a
PF
408
409 assert_return(ret, -EINVAL);
410
411 client = new0(sd_dhcp6_client, 1);
412 if (!client)
413 return -ENOMEM;
414
415 client->n_ref = REFCNT_INIT;
416
f12abb48
PF
417 client->ia_na.type = DHCP6_OPTION_IA_NA;
418
139b011a
PF
419 client->index = -1;
420
a276e6d6
TG
421 /* initialize DUID */
422 client->duid.type = htobe16(DHCP6_DUID_EN);
423 client->duid.pen = htobe32(SYSTEMD_PEN);
424
425 r = sd_id128_get_machine(&machine_id);
426 if (r < 0)
427 return r;
428
429 /* a bit of snake-oil perhaps, but no need to expose the machine-id
430 directly */
431 siphash24(client->duid.id, &machine_id, sizeof(machine_id),
432 HASH_KEY.bytes);
433
139b011a
PF
434 *ret = client;
435 client = NULL;
436
437 return 0;
438}