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