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