]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/sd-ipv4ll.c
ipv4acd: rework how we pick ipv4ll addresses
[thirdparty/systemd.git] / src / libsystemd-network / sd-ipv4ll.c
1 /***
2 This file is part of systemd.
3
4 Copyright (C) 2014 Axis Communications AB. All rights reserved.
5 Copyright (C) 2015 Tom Gundersen
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <arpa/inet.h>
22 #include <errno.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "sd-ipv4acd.h"
28 #include "sd-ipv4ll.h"
29
30 #include "alloc-util.h"
31 #include "ether-addr-util.h"
32 #include "in-addr-util.h"
33 #include "list.h"
34 #include "random-util.h"
35 #include "siphash24.h"
36 #include "sparse-endian.h"
37 #include "util.h"
38
39 #define IPV4LL_NETWORK UINT32_C(0xA9FE0000)
40 #define IPV4LL_NETMASK UINT32_C(0xFFFF0000)
41
42 #define IPV4LL_DONT_DESTROY(ll) \
43 _cleanup_(sd_ipv4ll_unrefp) _unused_ sd_ipv4ll *_dont_destroy_##ll = sd_ipv4ll_ref(ll)
44
45 struct sd_ipv4ll {
46 unsigned n_ref;
47
48 sd_ipv4acd *acd;
49
50 be32_t address; /* the address pushed to ACD */
51 struct ether_addr mac;
52
53 struct {
54 le64_t value;
55 le64_t generation;
56 } seed;
57 bool seed_set;
58
59 /* External */
60 be32_t claimed_address;
61
62 sd_ipv4ll_callback_t callback;
63 void* userdata;
64 };
65
66 static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, void *userdata);
67
68 sd_ipv4ll *sd_ipv4ll_ref(sd_ipv4ll *ll) {
69 if (!ll)
70 return NULL;
71
72 assert(ll->n_ref >= 1);
73 ll->n_ref++;
74
75 return ll;
76 }
77
78 sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll) {
79 if (!ll)
80 return NULL;
81
82 assert(ll->n_ref >= 1);
83 ll->n_ref--;
84
85 if (ll->n_ref > 0)
86 return NULL;
87
88 sd_ipv4acd_unref(ll->acd);
89 free(ll);
90
91 return NULL;
92 }
93
94 int sd_ipv4ll_new(sd_ipv4ll **ret) {
95 _cleanup_(sd_ipv4ll_unrefp) sd_ipv4ll *ll = NULL;
96 int r;
97
98 assert_return(ret, -EINVAL);
99
100 ll = new0(sd_ipv4ll, 1);
101 if (!ll)
102 return -ENOMEM;
103
104 ll->n_ref = 1;
105
106 r = sd_ipv4acd_new(&ll->acd);
107 if (r < 0)
108 return r;
109
110 r = sd_ipv4acd_set_callback(ll->acd, ipv4ll_on_acd, ll);
111 if (r < 0)
112 return r;
113
114 *ret = ll;
115 ll = NULL;
116
117 return 0;
118 }
119
120 int sd_ipv4ll_stop(sd_ipv4ll *ll) {
121 assert_return(ll, -EINVAL);
122
123 return sd_ipv4acd_stop(ll->acd);
124 }
125
126 int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int ifindex) {
127 assert_return(ll, -EINVAL);
128 assert_return(ifindex > 0, -EINVAL);
129 assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
130
131 return sd_ipv4acd_set_ifindex(ll->acd, ifindex);
132 }
133
134 int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) {
135 int r;
136
137 assert_return(ll, -EINVAL);
138 assert_return(addr, -EINVAL);
139 assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
140
141 r = sd_ipv4acd_set_mac(ll->acd, addr);
142 if (r < 0)
143 return r;
144
145 ll->mac = *addr;
146 return 0;
147 }
148
149 int sd_ipv4ll_detach_event(sd_ipv4ll *ll) {
150 assert_return(ll, -EINVAL);
151
152 return sd_ipv4acd_detach_event(ll->acd);
153 }
154
155 int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority) {
156 assert_return(ll, -EINVAL);
157
158 return sd_ipv4acd_attach_event(ll->acd, event, priority);
159 }
160
161 int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata) {
162 assert_return(ll, -EINVAL);
163
164 ll->callback = cb;
165 ll->userdata = userdata;
166
167 return 0;
168 }
169
170 int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address) {
171 assert_return(ll, -EINVAL);
172 assert_return(address, -EINVAL);
173
174 if (ll->claimed_address == 0)
175 return -ENOENT;
176
177 address->s_addr = ll->claimed_address;
178
179 return 0;
180 }
181
182 int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint64_t seed) {
183 assert_return(ll, -EINVAL);
184 assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
185
186 ll->seed.value = htole64(seed);
187 ll->seed_set = true;
188
189 return 0;
190 }
191
192 int sd_ipv4ll_is_running(sd_ipv4ll *ll) {
193 assert_return(ll, false);
194
195 return sd_ipv4acd_is_running(ll->acd);
196 }
197
198 static bool ipv4ll_address_is_valid(const struct in_addr *address) {
199 uint32_t addr;
200
201 assert(address);
202
203 if (!in_addr_is_link_local(AF_INET, (const union in_addr_union *) address))
204 return false;
205
206 addr = be32toh(address->s_addr);
207
208 if ((addr & 0x0000FF00) == 0x0000 ||
209 (addr & 0x0000FF00) == 0xFF00)
210 return false;
211
212 return true;
213 }
214
215 int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address) {
216 int r;
217
218 assert_return(ll, -EINVAL);
219 assert_return(address, -EINVAL);
220 assert_return(ipv4ll_address_is_valid(address), -EINVAL);
221
222 r = sd_ipv4acd_set_address(ll->acd, address);
223 if (r < 0)
224 return r;
225
226 ll->address = address->s_addr;
227
228 return 0;
229 }
230
231 #define PICK_HASH_KEY SD_ID128_MAKE(15,ac,82,a6,d6,3f,49,78,98,77,5d,0c,69,02,94,0b)
232
233 static int ipv4ll_pick_address(sd_ipv4ll *ll) {
234 be32_t addr;
235
236 assert(ll);
237
238 do {
239 uint64_t h;
240
241 h = siphash24(&ll->seed, sizeof(ll->seed), PICK_HASH_KEY.bytes);
242
243 /* Increase the generation counter by one */
244 ll->seed.generation = htole64(le64toh(ll->seed.generation) + 1);
245
246 addr = htobe32((h & UINT32_C(0x0000FFFF)) | IPV4LL_NETWORK);
247 } while (addr == ll->address ||
248 (be32toh(addr) & 0x0000FF00) == 0x0000 ||
249 (be32toh(addr) & 0x0000FF00) == 0xFF00);
250
251 return sd_ipv4ll_set_address(ll, &(struct in_addr) { addr });
252 }
253
254 #define MAC_HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2)
255
256 int sd_ipv4ll_start(sd_ipv4ll *ll) {
257 int r;
258 bool picked_address = false;
259
260 assert_return(ll, -EINVAL);
261 assert_return(!ether_addr_is_null(&ll->mac), -EINVAL);
262 assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
263
264 /* If no random seed is set, generate some from the MAC address */
265 if (!ll->seed_set)
266 ll->seed.value = htole64(siphash24(ll->mac.ether_addr_octet, ETH_ALEN, MAC_HASH_KEY.bytes));
267
268 /* Restart the generation counter. */
269 ll->seed.generation = 0;
270
271 if (ll->address == 0) {
272 r = ipv4ll_pick_address(ll);
273 if (r < 0)
274 return r;
275
276 picked_address = true;
277 }
278
279 r = sd_ipv4acd_start(ll->acd);
280 if (r < 0) {
281
282 /* We couldn't start? If so, let's forget the picked address again, the user might make a change and
283 * retry, and we want the new data to take effect when picking an address. */
284 if (picked_address)
285 ll->address = 0;
286
287 return r;
288 }
289
290 return 0;
291 }
292
293 static void ipv4ll_client_notify(sd_ipv4ll *ll, int event) {
294 assert(ll);
295
296 if (ll->callback)
297 ll->callback(ll, event, ll->userdata);
298 }
299
300 void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata) {
301 sd_ipv4ll *ll = userdata;
302 IPV4LL_DONT_DESTROY(ll);
303 int r;
304
305 assert(acd);
306 assert(ll);
307
308 switch (event) {
309
310 case SD_IPV4ACD_EVENT_STOP:
311 ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP);
312 ll->claimed_address = 0;
313 break;
314
315 case SD_IPV4ACD_EVENT_BIND:
316 ll->claimed_address = ll->address;
317 ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_BIND);
318 break;
319
320 case SD_IPV4ACD_EVENT_CONFLICT:
321 /* if an address was already bound we must call up to the
322 user to handle this, otherwise we just try again */
323 if (ll->claimed_address != 0) {
324 ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_CONFLICT);
325
326 ll->claimed_address = 0;
327 } else {
328 r = ipv4ll_pick_address(ll);
329 if (r < 0)
330 goto error;
331
332 r = sd_ipv4acd_start(ll->acd);
333 if (r < 0)
334 goto error;
335 }
336
337 break;
338
339 default:
340 assert_not_reached("Invalid IPv4ACD event.");
341 }
342
343 return;
344
345 error:
346 ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP);
347 }