]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/sd-ipv4ll.c
tree-wide: use mfree more
[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 "string-util.h"
38 #include "util.h"
39
40 #define IPV4LL_NETWORK UINT32_C(0xA9FE0000)
41 #define IPV4LL_NETMASK UINT32_C(0xFFFF0000)
42
43 #define IPV4LL_DONT_DESTROY(ll) \
44 _cleanup_(sd_ipv4ll_unrefp) _unused_ sd_ipv4ll *_dont_destroy_##ll = sd_ipv4ll_ref(ll)
45
46 struct sd_ipv4ll {
47 unsigned n_ref;
48
49 sd_ipv4acd *acd;
50
51 be32_t address; /* the address pushed to ACD */
52 struct ether_addr mac;
53
54 struct {
55 le64_t value;
56 le64_t generation;
57 } seed;
58 bool seed_set;
59
60 /* External */
61 be32_t claimed_address;
62
63 sd_ipv4ll_callback_t callback;
64 void* userdata;
65 };
66
67 #define log_ipv4ll_errno(ll, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "IPV4LL: " fmt, ##__VA_ARGS__)
68 #define log_ipv4ll(ll, fmt, ...) log_ipv4ll_errno(ll, 0, fmt, ##__VA_ARGS__)
69
70 static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, void *userdata);
71
72 sd_ipv4ll *sd_ipv4ll_ref(sd_ipv4ll *ll) {
73 if (!ll)
74 return NULL;
75
76 assert(ll->n_ref >= 1);
77 ll->n_ref++;
78
79 return ll;
80 }
81
82 sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll) {
83 if (!ll)
84 return NULL;
85
86 assert(ll->n_ref >= 1);
87 ll->n_ref--;
88
89 if (ll->n_ref > 0)
90 return NULL;
91
92 sd_ipv4acd_unref(ll->acd);
93 return mfree(ll);
94 }
95
96 int sd_ipv4ll_new(sd_ipv4ll **ret) {
97 _cleanup_(sd_ipv4ll_unrefp) sd_ipv4ll *ll = NULL;
98 int r;
99
100 assert_return(ret, -EINVAL);
101
102 ll = new0(sd_ipv4ll, 1);
103 if (!ll)
104 return -ENOMEM;
105
106 ll->n_ref = 1;
107
108 r = sd_ipv4acd_new(&ll->acd);
109 if (r < 0)
110 return r;
111
112 r = sd_ipv4acd_set_callback(ll->acd, ipv4ll_on_acd, ll);
113 if (r < 0)
114 return r;
115
116 *ret = ll;
117 ll = NULL;
118
119 return 0;
120 }
121
122 int sd_ipv4ll_stop(sd_ipv4ll *ll) {
123 assert_return(ll, -EINVAL);
124
125 return sd_ipv4acd_stop(ll->acd);
126 }
127
128 int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int ifindex) {
129 assert_return(ll, -EINVAL);
130 assert_return(ifindex > 0, -EINVAL);
131 assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
132
133 return sd_ipv4acd_set_ifindex(ll->acd, ifindex);
134 }
135
136 int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) {
137 int r;
138
139 assert_return(ll, -EINVAL);
140 assert_return(addr, -EINVAL);
141 assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
142
143 r = sd_ipv4acd_set_mac(ll->acd, addr);
144 if (r < 0)
145 return r;
146
147 ll->mac = *addr;
148 return 0;
149 }
150
151 int sd_ipv4ll_detach_event(sd_ipv4ll *ll) {
152 assert_return(ll, -EINVAL);
153
154 return sd_ipv4acd_detach_event(ll->acd);
155 }
156
157 int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority) {
158 assert_return(ll, -EINVAL);
159
160 return sd_ipv4acd_attach_event(ll->acd, event, priority);
161 }
162
163 int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata) {
164 assert_return(ll, -EINVAL);
165
166 ll->callback = cb;
167 ll->userdata = userdata;
168
169 return 0;
170 }
171
172 int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address) {
173 assert_return(ll, -EINVAL);
174 assert_return(address, -EINVAL);
175
176 if (ll->claimed_address == 0)
177 return -ENOENT;
178
179 address->s_addr = ll->claimed_address;
180
181 return 0;
182 }
183
184 int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint64_t seed) {
185 assert_return(ll, -EINVAL);
186 assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
187
188 ll->seed.value = htole64(seed);
189 ll->seed_set = true;
190
191 return 0;
192 }
193
194 int sd_ipv4ll_is_running(sd_ipv4ll *ll) {
195 assert_return(ll, false);
196
197 return sd_ipv4acd_is_running(ll->acd);
198 }
199
200 static bool ipv4ll_address_is_valid(const struct in_addr *address) {
201 assert(address);
202
203 if (!in_addr_is_link_local(AF_INET, (const union in_addr_union *) address))
204 return false;
205
206 return !IN_SET(be32toh(address->s_addr) & 0x0000FF00U, 0x0000U, 0xFF00U);
207 }
208
209 int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address) {
210 int r;
211
212 assert_return(ll, -EINVAL);
213 assert_return(address, -EINVAL);
214 assert_return(ipv4ll_address_is_valid(address), -EINVAL);
215
216 r = sd_ipv4acd_set_address(ll->acd, address);
217 if (r < 0)
218 return r;
219
220 ll->address = address->s_addr;
221
222 return 0;
223 }
224
225 #define PICK_HASH_KEY SD_ID128_MAKE(15,ac,82,a6,d6,3f,49,78,98,77,5d,0c,69,02,94,0b)
226
227 static int ipv4ll_pick_address(sd_ipv4ll *ll) {
228 _cleanup_free_ char *address = NULL;
229 be32_t addr;
230
231 assert(ll);
232
233 do {
234 uint64_t h;
235
236 h = siphash24(&ll->seed, sizeof(ll->seed), PICK_HASH_KEY.bytes);
237
238 /* Increase the generation counter by one */
239 ll->seed.generation = htole64(le64toh(ll->seed.generation) + 1);
240
241 addr = htobe32((h & UINT32_C(0x0000FFFF)) | IPV4LL_NETWORK);
242 } while (addr == ll->address ||
243 IN_SET(be32toh(addr) & 0x0000FF00U, 0x0000U, 0xFF00U));
244
245 (void) in_addr_to_string(AF_INET, &(union in_addr_union) { .in.s_addr = addr }, &address);
246 log_ipv4ll(ll, "Picked new IP address %s.", strna(address));
247
248 return sd_ipv4ll_set_address(ll, &(struct in_addr) { addr });
249 }
250
251 #define MAC_HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2)
252
253 int sd_ipv4ll_start(sd_ipv4ll *ll) {
254 int r;
255 bool picked_address = false;
256
257 assert_return(ll, -EINVAL);
258 assert_return(!ether_addr_is_null(&ll->mac), -EINVAL);
259 assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
260
261 /* If no random seed is set, generate some from the MAC address */
262 if (!ll->seed_set)
263 ll->seed.value = htole64(siphash24(ll->mac.ether_addr_octet, ETH_ALEN, MAC_HASH_KEY.bytes));
264
265 /* Restart the generation counter. */
266 ll->seed.generation = 0;
267
268 if (ll->address == 0) {
269 r = ipv4ll_pick_address(ll);
270 if (r < 0)
271 return r;
272
273 picked_address = true;
274 }
275
276 r = sd_ipv4acd_start(ll->acd);
277 if (r < 0) {
278
279 /* We couldn't start? If so, let's forget the picked address again, the user might make a change and
280 * retry, and we want the new data to take effect when picking an address. */
281 if (picked_address)
282 ll->address = 0;
283
284 return r;
285 }
286
287 return 0;
288 }
289
290 static void ipv4ll_client_notify(sd_ipv4ll *ll, int event) {
291 assert(ll);
292
293 if (ll->callback)
294 ll->callback(ll, event, ll->userdata);
295 }
296
297 void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata) {
298 sd_ipv4ll *ll = userdata;
299 IPV4LL_DONT_DESTROY(ll);
300 int r;
301
302 assert(acd);
303 assert(ll);
304
305 switch (event) {
306
307 case SD_IPV4ACD_EVENT_STOP:
308 ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP);
309 ll->claimed_address = 0;
310 break;
311
312 case SD_IPV4ACD_EVENT_BIND:
313 ll->claimed_address = ll->address;
314 ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_BIND);
315 break;
316
317 case SD_IPV4ACD_EVENT_CONFLICT:
318 /* if an address was already bound we must call up to the
319 user to handle this, otherwise we just try again */
320 if (ll->claimed_address != 0) {
321 ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_CONFLICT);
322
323 ll->claimed_address = 0;
324 } else {
325 r = ipv4ll_pick_address(ll);
326 if (r < 0)
327 goto error;
328
329 r = sd_ipv4acd_start(ll->acd);
330 if (r < 0)
331 goto error;
332 }
333
334 break;
335
336 default:
337 assert_not_reached("Invalid IPv4ACD event.");
338 }
339
340 return;
341
342 error:
343 ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP);
344 }