]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/sd-ipv4ll.c
shared/install: use _cleanup_free_
[thirdparty/systemd.git] / src / libsystemd-network / sd-ipv4ll.c
CommitLineData
5c1d3fc9
UTL
1/***
2 This file is part of systemd.
3
4 Copyright (C) 2014 Axis Communications AB. All rights reserved.
5707940e 5 Copyright (C) 2015 Tom Gundersen
5c1d3fc9
UTL
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
07630cea 21#include <arpa/inet.h>
5c1d3fc9 22#include <errno.h>
5c1d3fc9 23#include <stdio.h>
07630cea
LP
24#include <stdlib.h>
25#include <string.h>
26
27#include "sd-ipv4acd.h"
28#include "sd-ipv4ll.h"
5c1d3fc9 29
b5efdb8a 30#include "alloc-util.h"
96a7979f 31#include "ether-addr-util.h"
129dc1b4 32#include "in-addr-util.h"
5c1d3fc9 33#include "list.h"
3df3e884 34#include "random-util.h"
e3dca008
TG
35#include "siphash24.h"
36#include "sparse-endian.h"
703945c1 37#include "string-util.h"
e3dca008 38#include "util.h"
5c1d3fc9 39
96a7979f
LP
40#define IPV4LL_NETWORK UINT32_C(0xA9FE0000)
41#define IPV4LL_NETMASK UINT32_C(0xFFFF0000)
5c1d3fc9 42
b45e4eb6 43#define IPV4LL_DONT_DESTROY(ll) \
4afd3348 44 _cleanup_(sd_ipv4ll_unrefp) _unused_ sd_ipv4ll *_dont_destroy_##ll = sd_ipv4ll_ref(ll)
b45e4eb6 45
5c1d3fc9 46struct sd_ipv4ll {
9c8e3101 47 unsigned n_ref;
56cd007a 48
e3dca008 49 sd_ipv4acd *acd;
96a7979f 50
e3dca008 51 be32_t address; /* the address pushed to ACD */
96a7979f
LP
52 struct ether_addr mac;
53
54 struct {
55 le64_t value;
56 le64_t generation;
57 } seed;
58 bool seed_set;
e3dca008 59
5c1d3fc9
UTL
60 /* External */
61 be32_t claimed_address;
96a7979f 62
45aa74c7 63 sd_ipv4ll_callback_t callback;
5c1d3fc9
UTL
64 void* userdata;
65};
66
703945c1
LP
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
96a7979f
LP
70static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, void *userdata);
71
b45e4eb6
TG
72sd_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
82sd_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
e3dca008 92 sd_ipv4acd_unref(ll->acd);
b45e4eb6
TG
93 free(ll);
94
95 return NULL;
96}
97
b45e4eb6 98int sd_ipv4ll_new(sd_ipv4ll **ret) {
4afd3348 99 _cleanup_(sd_ipv4ll_unrefp) sd_ipv4ll *ll = NULL;
e3dca008 100 int r;
b45e4eb6
TG
101
102 assert_return(ret, -EINVAL);
103
104 ll = new0(sd_ipv4ll, 1);
105 if (!ll)
106 return -ENOMEM;
107
0c28d288
LP
108 ll->n_ref = 1;
109
e3dca008
TG
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
b45e4eb6
TG
118 *ret = ll;
119 ll = NULL;
120
121 return 0;
122}
123
b45e4eb6 124int sd_ipv4ll_stop(sd_ipv4ll *ll) {
e3dca008 125 assert_return(ll, -EINVAL);
94a355a1 126
96a7979f 127 return sd_ipv4acd_stop(ll->acd);
5c1d3fc9
UTL
128}
129
2f8e7633 130int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int ifindex) {
e3dca008 131 assert_return(ll, -EINVAL);
2f8e7633 132 assert_return(ifindex > 0, -EINVAL);
96a7979f 133 assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
5c1d3fc9 134
2f8e7633 135 return sd_ipv4acd_set_ifindex(ll->acd, ifindex);
5c1d3fc9
UTL
136}
137
e3dca008 138int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) {
b26f7e8e
TG
139 int r;
140
e3dca008 141 assert_return(ll, -EINVAL);
96a7979f
LP
142 assert_return(addr, -EINVAL);
143 assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
b26f7e8e 144
96a7979f
LP
145 r = sd_ipv4acd_set_mac(ll->acd, addr);
146 if (r < 0)
147 return r;
5c1d3fc9 148
96a7979f
LP
149 ll->mac = *addr;
150 return 0;
5c1d3fc9
UTL
151}
152
153int sd_ipv4ll_detach_event(sd_ipv4ll *ll) {
154 assert_return(ll, -EINVAL);
155
e3dca008 156 return sd_ipv4acd_detach_event(ll->acd);
5c1d3fc9
UTL
157}
158
32d20645 159int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority) {
5c1d3fc9 160 assert_return(ll, -EINVAL);
5c1d3fc9 161
73e94c0d 162 return sd_ipv4acd_attach_event(ll->acd, event, priority);
5c1d3fc9
UTL
163}
164
ccf86354 165int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata) {
5c1d3fc9
UTL
166 assert_return(ll, -EINVAL);
167
45aa74c7 168 ll->callback = cb;
5c1d3fc9
UTL
169 ll->userdata = userdata;
170
171 return 0;
172}
173
9ed794a3 174int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address) {
5c1d3fc9
UTL
175 assert_return(ll, -EINVAL);
176 assert_return(address, -EINVAL);
177
ece174c5 178 if (ll->claimed_address == 0)
5c1d3fc9 179 return -ENOENT;
5c1d3fc9
UTL
180
181 address->s_addr = ll->claimed_address;
e3dca008 182
5c1d3fc9
UTL
183 return 0;
184}
185
38958cd6 186int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint64_t seed) {
b5db00e5 187 assert_return(ll, -EINVAL);
96a7979f 188 assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
d9bf4f8c 189
96a7979f
LP
190 ll->seed.value = htole64(seed);
191 ll->seed_set = true;
b5db00e5 192
e3dca008 193 return 0;
b5db00e5
UTL
194}
195
04c01369 196int sd_ipv4ll_is_running(sd_ipv4ll *ll) {
75677581 197 assert_return(ll, false);
aba496a5 198
e3dca008 199 return sd_ipv4acd_is_running(ll->acd);
aba496a5
UTL
200}
201
129dc1b4 202static bool ipv4ll_address_is_valid(const struct in_addr *address) {
129dc1b4
TG
203 assert(address);
204
205 if (!in_addr_is_link_local(AF_INET, (const union in_addr_union *) address))
206 return false;
207
ae06d1be 208 return !IN_SET(be32toh(address->s_addr) & 0x0000FF00U, 0x0000U, 0xFF00U);
129dc1b4
TG
209}
210
211int 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
96a7979f
LP
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
e3dca008 229static int ipv4ll_pick_address(sd_ipv4ll *ll) {
703945c1 230 _cleanup_free_ char *address = NULL;
e3dca008 231 be32_t addr;
5c1d3fc9 232
e3dca008 233 assert(ll);
4d978a46 234
e3dca008 235 do {
96a7979f 236 uint64_t h;
5c1d3fc9 237
96a7979f 238 h = siphash24(&ll->seed, sizeof(ll->seed), PICK_HASH_KEY.bytes);
5c1d3fc9 239
96a7979f
LP
240 /* Increase the generation counter by one */
241 ll->seed.generation = htole64(le64toh(ll->seed.generation) + 1);
b5db00e5 242
96a7979f
LP
243 addr = htobe32((h & UINT32_C(0x0000FFFF)) | IPV4LL_NETWORK);
244 } while (addr == ll->address ||
ae06d1be 245 IN_SET(be32toh(addr) & 0x0000FF00U, 0x0000U, 0xFF00U));
96a7979f 246
703945c1
LP
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
96a7979f 250 return sd_ipv4ll_set_address(ll, &(struct in_addr) { addr });
e3dca008
TG
251}
252
96a7979f
LP
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
e3dca008
TG
255int sd_ipv4ll_start(sd_ipv4ll *ll) {
256 int r;
96a7979f 257 bool picked_address = false;
e3dca008
TG
258
259 assert_return(ll, -EINVAL);
96a7979f
LP
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;
b5db00e5
UTL
269
270 if (ll->address == 0) {
e3dca008 271 r = ipv4ll_pick_address(ll);
b5db00e5 272 if (r < 0)
e3dca008 273 return r;
96a7979f
LP
274
275 picked_address = true;
b5db00e5 276 }
5c1d3fc9 277
e3dca008 278 r = sd_ipv4acd_start(ll->acd);
96a7979f
LP
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
e3dca008 286 return r;
96a7979f 287 }
996d1697 288
e3dca008
TG
289 return 0;
290}
996d1697 291
e3dca008
TG
292static void ipv4ll_client_notify(sd_ipv4ll *ll, int event) {
293 assert(ll);
5c1d3fc9 294
45aa74c7
LP
295 if (ll->callback)
296 ll->callback(ll, event, ll->userdata);
e3dca008 297}
5c1d3fc9 298
e3dca008
TG
299void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata) {
300 sd_ipv4ll *ll = userdata;
301 IPV4LL_DONT_DESTROY(ll);
302 int r;
5c1d3fc9 303
e3dca008
TG
304 assert(acd);
305 assert(ll);
9021bb9f 306
e3dca008 307 switch (event) {
73e94c0d 308
2237aa02 309 case SD_IPV4ACD_EVENT_STOP:
be19c5b5 310 ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP);
e3dca008 311 ll->claimed_address = 0;
e3dca008 312 break;
73e94c0d 313
2237aa02 314 case SD_IPV4ACD_EVENT_BIND:
e3dca008 315 ll->claimed_address = ll->address;
be19c5b5 316 ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_BIND);
e3dca008 317 break;
73e94c0d 318
2237aa02 319 case SD_IPV4ACD_EVENT_CONFLICT:
e3dca008
TG
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) {
be19c5b5 323 ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_CONFLICT);
e3dca008
TG
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;
96a7979f 337
e3dca008
TG
338 default:
339 assert_not_reached("Invalid IPv4ACD event.");
340 }
341
342 return;
343
344error:
be19c5b5 345 ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP);
5c1d3fc9 346}