1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright (C) 2014 Axis Communications AB. All rights reserved.
6 Copyright (C) 2015 Tom Gundersen
16 #include "sd-ipv4acd.h"
17 #include "sd-ipv4ll.h"
19 #include "alloc-util.h"
20 #include "ether-addr-util.h"
21 #include "in-addr-util.h"
23 #include "random-util.h"
24 #include "siphash24.h"
25 #include "sparse-endian.h"
26 #include "string-util.h"
29 #define IPV4LL_NETWORK UINT32_C(0xA9FE0000)
30 #define IPV4LL_NETMASK UINT32_C(0xFFFF0000)
32 #define IPV4LL_DONT_DESTROY(ll) \
33 _cleanup_(sd_ipv4ll_unrefp) _unused_ sd_ipv4ll *_dont_destroy_##ll = sd_ipv4ll_ref(ll)
40 be32_t address
; /* the address pushed to ACD */
41 struct ether_addr mac
;
50 be32_t claimed_address
;
52 sd_ipv4ll_callback_t callback
;
56 #define log_ipv4ll_errno(ll, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "IPV4LL: " fmt, ##__VA_ARGS__)
57 #define log_ipv4ll(ll, fmt, ...) log_ipv4ll_errno(ll, 0, fmt, ##__VA_ARGS__)
59 static void ipv4ll_on_acd(sd_ipv4acd
*ll
, int event
, void *userdata
);
61 sd_ipv4ll
*sd_ipv4ll_ref(sd_ipv4ll
*ll
) {
65 assert(ll
->n_ref
>= 1);
71 sd_ipv4ll
*sd_ipv4ll_unref(sd_ipv4ll
*ll
) {
75 assert(ll
->n_ref
>= 1);
81 sd_ipv4acd_unref(ll
->acd
);
85 int sd_ipv4ll_new(sd_ipv4ll
**ret
) {
86 _cleanup_(sd_ipv4ll_unrefp
) sd_ipv4ll
*ll
= NULL
;
89 assert_return(ret
, -EINVAL
);
91 ll
= new0(sd_ipv4ll
, 1);
97 r
= sd_ipv4acd_new(&ll
->acd
);
101 r
= sd_ipv4acd_set_callback(ll
->acd
, ipv4ll_on_acd
, ll
);
110 int sd_ipv4ll_stop(sd_ipv4ll
*ll
) {
111 assert_return(ll
, -EINVAL
);
113 return sd_ipv4acd_stop(ll
->acd
);
116 int sd_ipv4ll_set_ifindex(sd_ipv4ll
*ll
, int ifindex
) {
117 assert_return(ll
, -EINVAL
);
118 assert_return(ifindex
> 0, -EINVAL
);
119 assert_return(sd_ipv4ll_is_running(ll
) == 0, -EBUSY
);
121 return sd_ipv4acd_set_ifindex(ll
->acd
, ifindex
);
124 int sd_ipv4ll_set_mac(sd_ipv4ll
*ll
, const struct ether_addr
*addr
) {
127 assert_return(ll
, -EINVAL
);
128 assert_return(addr
, -EINVAL
);
129 assert_return(sd_ipv4ll_is_running(ll
) == 0, -EBUSY
);
131 r
= sd_ipv4acd_set_mac(ll
->acd
, addr
);
139 int sd_ipv4ll_detach_event(sd_ipv4ll
*ll
) {
140 assert_return(ll
, -EINVAL
);
142 return sd_ipv4acd_detach_event(ll
->acd
);
145 int sd_ipv4ll_attach_event(sd_ipv4ll
*ll
, sd_event
*event
, int64_t priority
) {
146 assert_return(ll
, -EINVAL
);
148 return sd_ipv4acd_attach_event(ll
->acd
, event
, priority
);
151 int sd_ipv4ll_set_callback(sd_ipv4ll
*ll
, sd_ipv4ll_callback_t cb
, void *userdata
) {
152 assert_return(ll
, -EINVAL
);
155 ll
->userdata
= userdata
;
160 int sd_ipv4ll_get_address(sd_ipv4ll
*ll
, struct in_addr
*address
) {
161 assert_return(ll
, -EINVAL
);
162 assert_return(address
, -EINVAL
);
164 if (ll
->claimed_address
== 0)
167 address
->s_addr
= ll
->claimed_address
;
172 int sd_ipv4ll_set_address_seed(sd_ipv4ll
*ll
, uint64_t seed
) {
173 assert_return(ll
, -EINVAL
);
174 assert_return(sd_ipv4ll_is_running(ll
) == 0, -EBUSY
);
176 ll
->seed
.value
= htole64(seed
);
182 int sd_ipv4ll_is_running(sd_ipv4ll
*ll
) {
183 assert_return(ll
, false);
185 return sd_ipv4acd_is_running(ll
->acd
);
188 static bool ipv4ll_address_is_valid(const struct in_addr
*address
) {
191 if (!in_addr_is_link_local(AF_INET
, (const union in_addr_union
*) address
))
194 return !IN_SET(be32toh(address
->s_addr
) & 0x0000FF00U
, 0x0000U
, 0xFF00U
);
197 int sd_ipv4ll_set_address(sd_ipv4ll
*ll
, const struct in_addr
*address
) {
200 assert_return(ll
, -EINVAL
);
201 assert_return(address
, -EINVAL
);
202 assert_return(ipv4ll_address_is_valid(address
), -EINVAL
);
204 r
= sd_ipv4acd_set_address(ll
->acd
, address
);
208 ll
->address
= address
->s_addr
;
213 #define PICK_HASH_KEY SD_ID128_MAKE(15,ac,82,a6,d6,3f,49,78,98,77,5d,0c,69,02,94,0b)
215 static int ipv4ll_pick_address(sd_ipv4ll
*ll
) {
216 _cleanup_free_
char *address
= NULL
;
224 h
= siphash24(&ll
->seed
, sizeof(ll
->seed
), PICK_HASH_KEY
.bytes
);
226 /* Increase the generation counter by one */
227 ll
->seed
.generation
= htole64(le64toh(ll
->seed
.generation
) + 1);
229 addr
= htobe32((h
& UINT32_C(0x0000FFFF)) | IPV4LL_NETWORK
);
230 } while (addr
== ll
->address
||
231 IN_SET(be32toh(addr
) & 0x0000FF00U
, 0x0000U
, 0xFF00U
));
233 (void) in_addr_to_string(AF_INET
, &(union in_addr_union
) { .in
.s_addr
= addr
}, &address
);
234 log_ipv4ll(ll
, "Picked new IP address %s.", strna(address
));
236 return sd_ipv4ll_set_address(ll
, &(struct in_addr
) { addr
});
239 int sd_ipv4ll_restart(sd_ipv4ll
*ll
) {
242 return sd_ipv4ll_start(ll
);
245 #define MAC_HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2)
247 int sd_ipv4ll_start(sd_ipv4ll
*ll
) {
249 bool picked_address
= false;
251 assert_return(ll
, -EINVAL
);
252 assert_return(!ether_addr_is_null(&ll
->mac
), -EINVAL
);
253 assert_return(sd_ipv4ll_is_running(ll
) == 0, -EBUSY
);
255 /* If no random seed is set, generate some from the MAC address */
257 ll
->seed
.value
= htole64(siphash24(ll
->mac
.ether_addr_octet
, ETH_ALEN
, MAC_HASH_KEY
.bytes
));
259 /* Restart the generation counter. */
260 ll
->seed
.generation
= 0;
262 if (ll
->address
== 0) {
263 r
= ipv4ll_pick_address(ll
);
267 picked_address
= true;
270 r
= sd_ipv4acd_start(ll
->acd
);
273 /* We couldn't start? If so, let's forget the picked address again, the user might make a change and
274 * retry, and we want the new data to take effect when picking an address. */
284 static void ipv4ll_client_notify(sd_ipv4ll
*ll
, int event
) {
288 ll
->callback(ll
, event
, ll
->userdata
);
291 void ipv4ll_on_acd(sd_ipv4acd
*acd
, int event
, void *userdata
) {
292 sd_ipv4ll
*ll
= userdata
;
293 IPV4LL_DONT_DESTROY(ll
);
301 case SD_IPV4ACD_EVENT_STOP
:
302 ipv4ll_client_notify(ll
, SD_IPV4LL_EVENT_STOP
);
303 ll
->claimed_address
= 0;
306 case SD_IPV4ACD_EVENT_BIND
:
307 ll
->claimed_address
= ll
->address
;
308 ipv4ll_client_notify(ll
, SD_IPV4LL_EVENT_BIND
);
311 case SD_IPV4ACD_EVENT_CONFLICT
:
312 /* if an address was already bound we must call up to the
313 user to handle this, otherwise we just try again */
314 if (ll
->claimed_address
!= 0) {
315 ipv4ll_client_notify(ll
, SD_IPV4LL_EVENT_CONFLICT
);
317 ll
->claimed_address
= 0;
319 r
= ipv4ll_pick_address(ll
);
323 r
= sd_ipv4acd_start(ll
->acd
);
331 assert_not_reached("Invalid IPv4ACD event.");
337 ipv4ll_client_notify(ll
, SD_IPV4LL_EVENT_STOP
);