chiark / gitweb /
sd-dhcp6-client: Add DHCPv6 IAID functionality
[elogind.git] / src / libsystemd-network / sd-dhcp6-client.c
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
25 #include "udev.h"
26 #include "udev-util.h"
27 #include "virt.h"
28 #include "siphash24.h"
29 #include "util.h"
30 #include "refcnt.h"
31
32 #include "network-internal.h"
33 #include "sd-dhcp6-client.h"
34 #include "dhcp6-protocol.h"
35 #include "dhcp6-internal.h"
36
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
40 struct 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;
48         DHCP6IA ia_na;
49         sd_dhcp6_client_cb_t cb;
50         void *userdata;
51
52         struct duid_en {
53                 uint16_t type; /* DHCP6_DUID_EN */
54                 uint32_t pen;
55                 uint8_t id[8];
56         } _packed_ duid;
57 };
58
59 int sd_dhcp6_client_set_callback(sd_dhcp6_client *client,
60                                  sd_dhcp6_client_cb_t cb, void *userdata)
61 {
62         assert_return(client, -EINVAL);
63
64         client->cb = cb;
65         client->userdata = userdata;
66
67         return 0;
68 }
69
70 int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index)
71 {
72         assert_return(client, -EINVAL);
73         assert_return(interface_index >= -1, -EINVAL);
74
75         client->index = interface_index;
76
77         return 0;
78 }
79
80 int sd_dhcp6_client_set_mac(sd_dhcp6_client *client,
81                             const struct ether_addr *mac_addr)
82 {
83         assert_return(client, -EINVAL);
84
85         if (mac_addr)
86                 memcpy(&client->mac_addr, mac_addr, sizeof(client->mac_addr));
87         else
88                 memset(&client->mac_addr, 0x00, sizeof(client->mac_addr));
89
90         return 0;
91 }
92
93 static sd_dhcp6_client *client_notify(sd_dhcp6_client *client, int event) {
94         if (client->cb) {
95                 client = sd_dhcp6_client_ref(client);
96                 client->cb(client, event, client->userdata);
97                 client = sd_dhcp6_client_unref(client);
98         }
99
100         return client;
101 }
102
103 static int client_initialize(sd_dhcp6_client *client)
104 {
105         assert_return(client, -EINVAL);
106
107         client->ia_na.timeout_t1 =
108                 sd_event_source_unref(client->ia_na.timeout_t1);
109         client->ia_na.timeout_t2 =
110                 sd_event_source_unref(client->ia_na.timeout_t2);
111
112         client->state = DHCP6_STATE_STOPPED;
113
114         return 0;
115 }
116
117 static sd_dhcp6_client *client_stop(sd_dhcp6_client *client, int error) {
118         assert_return(client, NULL);
119
120         client = client_notify(client, error);
121         if (client)
122                 client_initialize(client);
123
124         return client;
125 }
126
127 static int client_ensure_iaid(sd_dhcp6_client *client) {
128         const char *name = NULL;
129         uint64_t id;
130
131         assert(client);
132
133         if (client->ia_na.id)
134                 return 0;
135
136         if (detect_container(NULL) <= 0) {
137                 /* not in a container, udev will be around */
138                 _cleanup_udev_unref_ struct udev *udev;
139                 _cleanup_udev_device_unref_ struct udev_device *device;
140                 char ifindex_str[2 + DECIMAL_STR_MAX(int)];
141
142                 udev = udev_new();
143                 if (!udev)
144                         return -ENOMEM;
145
146                 sprintf(ifindex_str, "n%d", client->index);
147                 device = udev_device_new_from_device_id(udev, ifindex_str);
148                 if (!device)
149                         return -errno;
150
151                 if (udev_device_get_is_initialized(device) <= 0)
152                         /* not yet ready */
153                         return -EBUSY;
154
155                 name = net_get_name(device);
156         }
157
158         if (name)
159                 siphash24((uint8_t*)&id, name, strlen(name), HASH_KEY.bytes);
160         else
161                 /* fall back to mac address if no predictable name available */
162                 siphash24((uint8_t*)&id, &client->mac_addr, ETH_ALEN,
163                           HASH_KEY.bytes);
164
165         /* fold into 32 bits */
166         client->ia_na.id = (id & 0xffffffff) ^ (id >> 32);
167
168         return 0;
169 }
170
171 static int client_start(sd_dhcp6_client *client)
172 {
173         int r;
174
175         assert_return(client, -EINVAL);
176         assert_return(client->event, -EINVAL);
177         assert_return(client->index > 0, -EINVAL);
178
179         r = client_ensure_iaid(client);
180         if (r < 0)
181                 return r;
182
183         return 0;
184 }
185
186 int sd_dhcp6_client_stop(sd_dhcp6_client *client)
187 {
188         client_stop(client, DHCP6_EVENT_STOP);
189
190         return 0;
191 }
192
193 int sd_dhcp6_client_start(sd_dhcp6_client *client)
194 {
195         int r = 0;
196
197         assert_return(client, -EINVAL);
198         assert_return(client->event, -EINVAL);
199         assert_return(client->index > 0, -EINVAL);
200
201         r = client_initialize(client);
202         if (r < 0)
203                 return r;
204
205         return client_start(client);
206 }
207
208 int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event,
209                                  int priority)
210 {
211         int r;
212
213         assert_return(client, -EINVAL);
214         assert_return(!client->event, -EBUSY);
215
216         if (event)
217                 client->event = sd_event_ref(event);
218         else {
219                 r = sd_event_default(&client->event);
220                 if (r < 0)
221                         return 0;
222         }
223
224         client->event_priority = priority;
225
226         return 0;
227 }
228
229 int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) {
230         assert_return(client, -EINVAL);
231
232         client->event = sd_event_unref(client->event);
233
234         return 0;
235 }
236
237 sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
238         if (!client)
239                 return NULL;
240
241         return client->event;
242 }
243
244 sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client) {
245         if (client)
246                 assert_se(REFCNT_INC(client->n_ref) >= 2);
247
248         return client;
249 }
250
251 sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
252         if (client && REFCNT_DEC(client->n_ref) <= 0) {
253                 client_initialize(client);
254
255                 sd_dhcp6_client_detach_event(client);
256
257                 free(client);
258
259                 return NULL;
260         }
261
262         return client;
263 }
264
265 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_client*, sd_dhcp6_client_unref);
266 #define _cleanup_dhcp6_client_free_ _cleanup_(sd_dhcp6_client_unrefp)
267
268 int sd_dhcp6_client_new(sd_dhcp6_client **ret)
269 {
270         _cleanup_dhcp6_client_free_ sd_dhcp6_client *client = NULL;
271         sd_id128_t machine_id;
272         int r;
273
274         assert_return(ret, -EINVAL);
275
276         client = new0(sd_dhcp6_client, 1);
277         if (!client)
278                 return -ENOMEM;
279
280         client->n_ref = REFCNT_INIT;
281
282         client->ia_na.type = DHCP6_OPTION_IA_NA;
283
284         client->index = -1;
285
286         /* initialize DUID */
287         client->duid.type = htobe16(DHCP6_DUID_EN);
288         client->duid.pen = htobe32(SYSTEMD_PEN);
289
290         r = sd_id128_get_machine(&machine_id);
291         if (r < 0)
292                 return r;
293
294         /* a bit of snake-oil perhaps, but no need to expose the machine-id
295            directly */
296         siphash24(client->duid.id, &machine_id, sizeof(machine_id),
297                   HASH_KEY.bytes);
298
299         *ret = client;
300         client = NULL;
301
302         return 0;
303 }