/src/systemd/src/libsystemd-network/sd-ipv4ll.c
Line | Count | Source |
1 | | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | | /*** |
3 | | Copyright © 2014 Axis Communications AB. All rights reserved. |
4 | | ***/ |
5 | | |
6 | | #include "sd-id128.h" |
7 | | #include "sd-ipv4acd.h" |
8 | | #include "sd-ipv4ll.h" |
9 | | |
10 | | #include "alloc-util.h" |
11 | | #include "ether-addr-util.h" |
12 | | #include "in-addr-util.h" |
13 | | #include "network-common.h" |
14 | | #include "siphash24.h" |
15 | | #include "sparse-endian.h" |
16 | | |
17 | 0 | #define IPV4LL_NETWORK UINT32_C(0xA9FE0000) |
18 | | #define IPV4LL_NETMASK UINT32_C(0xFFFF0000) |
19 | | |
20 | | #define IPV4LL_DONT_DESTROY(ll) \ |
21 | 0 | _cleanup_(sd_ipv4ll_unrefp) _unused_ sd_ipv4ll *_dont_destroy_##ll = sd_ipv4ll_ref(ll) |
22 | | |
23 | | struct sd_ipv4ll { |
24 | | unsigned n_ref; |
25 | | |
26 | | sd_ipv4acd *acd; |
27 | | |
28 | | be32_t address; /* the address pushed to ACD */ |
29 | | struct ether_addr mac; |
30 | | |
31 | | struct { |
32 | | le64_t value; |
33 | | le64_t generation; |
34 | | } seed; |
35 | | bool seed_set; |
36 | | |
37 | | /* External */ |
38 | | be32_t claimed_address; |
39 | | |
40 | | sd_ipv4ll_callback_t callback; |
41 | | void *userdata; |
42 | | |
43 | | sd_ipv4ll_check_mac_callback_t check_mac_callback; |
44 | | void *check_mac_userdata; |
45 | | }; |
46 | | |
47 | | #define log_ipv4ll_errno(ll, error, fmt, ...) \ |
48 | | log_interface_prefix_full_errno( \ |
49 | | "IPv4LL: ", \ |
50 | | sd_ipv4ll, ll, \ |
51 | | error, fmt, ##__VA_ARGS__) |
52 | | #define log_ipv4ll(ll, fmt, ...) \ |
53 | 0 | log_interface_prefix_full_errno_zerook( \ |
54 | 0 | "IPv4LL: ", \ |
55 | 0 | sd_ipv4ll, ll, \ |
56 | 0 | 0, fmt, ##__VA_ARGS__) |
57 | | |
58 | | static void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata); |
59 | | static int ipv4ll_check_mac(sd_ipv4acd *acd, const struct ether_addr *mac, void *userdata); |
60 | | |
61 | 0 | static sd_ipv4ll *ipv4ll_free(sd_ipv4ll *ll) { |
62 | 0 | assert(ll); |
63 | |
|
64 | 0 | sd_ipv4acd_unref(ll->acd); |
65 | 0 | return mfree(ll); |
66 | 0 | } |
67 | | |
68 | 0 | DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_ipv4ll, sd_ipv4ll, ipv4ll_free); Unexecuted instantiation: sd_ipv4ll_ref Unexecuted instantiation: sd_ipv4ll_unref |
69 | 0 |
|
70 | 0 | int sd_ipv4ll_new(sd_ipv4ll **ret) { |
71 | 0 | _cleanup_(sd_ipv4ll_unrefp) sd_ipv4ll *ll = NULL; |
72 | 0 | int r; |
73 | |
|
74 | 0 | assert_return(ret, -EINVAL); |
75 | | |
76 | 0 | ll = new0(sd_ipv4ll, 1); |
77 | 0 | if (!ll) |
78 | 0 | return -ENOMEM; |
79 | | |
80 | 0 | ll->n_ref = 1; |
81 | |
|
82 | 0 | r = sd_ipv4acd_new(&ll->acd); |
83 | 0 | if (r < 0) |
84 | 0 | return r; |
85 | | |
86 | 0 | r = sd_ipv4acd_set_callback(ll->acd, ipv4ll_on_acd, ll); |
87 | 0 | if (r < 0) |
88 | 0 | return r; |
89 | | |
90 | 0 | r = sd_ipv4acd_set_check_mac_callback(ll->acd, ipv4ll_check_mac, ll); |
91 | 0 | if (r < 0) |
92 | 0 | return r; |
93 | | |
94 | 0 | *ret = TAKE_PTR(ll); |
95 | |
|
96 | 0 | return 0; |
97 | 0 | } |
98 | | |
99 | 0 | int sd_ipv4ll_stop(sd_ipv4ll *ll) { |
100 | 0 | if (!ll) |
101 | 0 | return 0; |
102 | | |
103 | 0 | return sd_ipv4acd_stop(ll->acd); |
104 | 0 | } |
105 | | |
106 | 0 | int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int ifindex) { |
107 | 0 | assert_return(ll, -EINVAL); |
108 | 0 | assert_return(ifindex > 0, -EINVAL); |
109 | 0 | assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); |
110 | | |
111 | 0 | return sd_ipv4acd_set_ifindex(ll->acd, ifindex); |
112 | 0 | } |
113 | | |
114 | 0 | int sd_ipv4ll_get_ifindex(sd_ipv4ll *ll) { |
115 | 0 | if (!ll) |
116 | 0 | return -EINVAL; |
117 | | |
118 | 0 | return sd_ipv4acd_get_ifindex(ll->acd); |
119 | 0 | } |
120 | | |
121 | 0 | int sd_ipv4ll_set_ifname(sd_ipv4ll *ll, const char *ifname) { |
122 | 0 | assert_return(ll, -EINVAL); |
123 | 0 | assert_return(ifname, -EINVAL); |
124 | | |
125 | 0 | return sd_ipv4acd_set_ifname(ll->acd, ifname); |
126 | 0 | } |
127 | | |
128 | 0 | int sd_ipv4ll_get_ifname(sd_ipv4ll *ll, const char **ret) { |
129 | 0 | assert_return(ll, -EINVAL); |
130 | | |
131 | 0 | return sd_ipv4acd_get_ifname(ll->acd, ret); |
132 | 0 | } |
133 | | |
134 | 0 | int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) { |
135 | 0 | int r; |
136 | |
|
137 | 0 | assert_return(ll, -EINVAL); |
138 | 0 | assert_return(addr, -EINVAL); |
139 | 0 | assert_return(!ether_addr_is_null(addr), -EINVAL); |
140 | | |
141 | 0 | r = sd_ipv4acd_set_mac(ll->acd, addr); |
142 | 0 | if (r < 0) |
143 | 0 | return r; |
144 | | |
145 | 0 | ll->mac = *addr; |
146 | 0 | return 0; |
147 | 0 | } |
148 | | |
149 | 0 | int sd_ipv4ll_set_timeout(sd_ipv4ll *ll, uint64_t usec) { |
150 | 0 | assert_return(ll, -EINVAL); |
151 | | |
152 | 0 | return sd_ipv4acd_set_timeout(ll->acd, usec); |
153 | 0 | } |
154 | | |
155 | 0 | int sd_ipv4ll_detach_event(sd_ipv4ll *ll) { |
156 | 0 | assert_return(ll, -EINVAL); |
157 | | |
158 | 0 | return sd_ipv4acd_detach_event(ll->acd); |
159 | 0 | } |
160 | | |
161 | 0 | int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority) { |
162 | 0 | assert_return(ll, -EINVAL); |
163 | | |
164 | 0 | return sd_ipv4acd_attach_event(ll->acd, event, priority); |
165 | 0 | } |
166 | | |
167 | 0 | int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata) { |
168 | 0 | assert_return(ll, -EINVAL); |
169 | | |
170 | 0 | ll->callback = cb; |
171 | 0 | ll->userdata = userdata; |
172 | |
|
173 | 0 | return 0; |
174 | 0 | } |
175 | | |
176 | 0 | int sd_ipv4ll_set_check_mac_callback(sd_ipv4ll *ll, sd_ipv4ll_check_mac_callback_t cb, void *userdata) { |
177 | 0 | assert_return(ll, -EINVAL); |
178 | | |
179 | 0 | ll->check_mac_callback = cb; |
180 | 0 | ll->check_mac_userdata = userdata; |
181 | |
|
182 | 0 | return 0; |
183 | 0 | } |
184 | | |
185 | 0 | int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address) { |
186 | 0 | assert_return(ll, -EINVAL); |
187 | 0 | assert_return(address, -EINVAL); |
188 | | |
189 | 0 | if (ll->claimed_address == 0) |
190 | 0 | return -ENOENT; |
191 | | |
192 | 0 | address->s_addr = ll->claimed_address; |
193 | |
|
194 | 0 | return 0; |
195 | 0 | } |
196 | | |
197 | 0 | int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint64_t seed) { |
198 | 0 | assert_return(ll, -EINVAL); |
199 | 0 | assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); |
200 | | |
201 | 0 | ll->seed.value = htole64(seed); |
202 | 0 | ll->seed_set = true; |
203 | |
|
204 | 0 | return 0; |
205 | 0 | } |
206 | | |
207 | 0 | int sd_ipv4ll_is_running(sd_ipv4ll *ll) { |
208 | 0 | if (!ll) |
209 | 0 | return false; |
210 | | |
211 | 0 | return sd_ipv4acd_is_running(ll->acd); |
212 | 0 | } |
213 | | |
214 | 0 | int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address) { |
215 | 0 | int r; |
216 | |
|
217 | 0 | assert_return(ll, -EINVAL); |
218 | 0 | assert_return(address, -EINVAL); |
219 | 0 | assert_return(in4_addr_is_link_local_dynamic(address), -EINVAL); |
220 | | |
221 | 0 | r = sd_ipv4acd_set_address(ll->acd, address); |
222 | 0 | if (r < 0) |
223 | 0 | return r; |
224 | | |
225 | 0 | ll->address = address->s_addr; |
226 | |
|
227 | 0 | return 0; |
228 | 0 | } |
229 | | |
230 | 0 | #define PICK_HASH_KEY SD_ID128_MAKE(15,ac,82,a6,d6,3f,49,78,98,77,5d,0c,69,02,94,0b) |
231 | | |
232 | 0 | static int ipv4ll_pick_address(sd_ipv4ll *ll) { |
233 | 0 | be32_t addr; |
234 | |
|
235 | 0 | assert(ll); |
236 | |
|
237 | 0 | do { |
238 | 0 | uint64_t h; |
239 | |
|
240 | 0 | h = siphash24(&ll->seed, sizeof(ll->seed), PICK_HASH_KEY.bytes); |
241 | | |
242 | | /* Increase the generation counter by one */ |
243 | 0 | ll->seed.generation = htole64(le64toh(ll->seed.generation) + 1); |
244 | |
|
245 | 0 | addr = htobe32((h & UINT32_C(0x0000FFFF)) | IPV4LL_NETWORK); |
246 | 0 | } while (addr == ll->address || |
247 | 0 | IN_SET(be32toh(addr) & 0x0000FF00U, 0x0000U, 0xFF00U)); |
248 | |
|
249 | 0 | log_ipv4ll(ll, "Picked new IP address %s.", IN4_ADDR_TO_STRING((const struct in_addr*) &addr)); |
250 | |
|
251 | 0 | return sd_ipv4ll_set_address(ll, &(struct in_addr) { addr }); |
252 | 0 | } |
253 | | |
254 | 0 | #define MAC_HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2) |
255 | | |
256 | 0 | static int ipv4ll_start_internal(sd_ipv4ll *ll, bool reset_generation) { |
257 | 0 | int r; |
258 | 0 | bool picked_address = false; |
259 | |
|
260 | 0 | assert_return(ll, -EINVAL); |
261 | 0 | assert_return(!ether_addr_is_null(&ll->mac), -EINVAL); |
262 | | |
263 | | /* If no random seed is set, generate some from the MAC address */ |
264 | 0 | if (!ll->seed_set) |
265 | 0 | ll->seed.value = htole64(siphash24(ll->mac.ether_addr_octet, ETH_ALEN, MAC_HASH_KEY.bytes)); |
266 | |
|
267 | 0 | if (reset_generation) |
268 | 0 | ll->seed.generation = 0; |
269 | |
|
270 | 0 | if (ll->address == 0) { |
271 | 0 | r = ipv4ll_pick_address(ll); |
272 | 0 | if (r < 0) |
273 | 0 | return r; |
274 | | |
275 | 0 | picked_address = true; |
276 | 0 | } |
277 | | |
278 | 0 | r = sd_ipv4acd_start(ll->acd, reset_generation); |
279 | 0 | 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 | 0 | if (picked_address) |
284 | 0 | ll->address = 0; |
285 | |
|
286 | 0 | return r; |
287 | 0 | } |
288 | | |
289 | 0 | return 1; |
290 | 0 | } |
291 | | |
292 | 0 | int sd_ipv4ll_start(sd_ipv4ll *ll) { |
293 | 0 | assert_return(ll, -EINVAL); |
294 | | |
295 | 0 | if (sd_ipv4ll_is_running(ll)) |
296 | 0 | return 0; |
297 | | |
298 | 0 | return ipv4ll_start_internal(ll, true); |
299 | 0 | } |
300 | | |
301 | 0 | int sd_ipv4ll_restart(sd_ipv4ll *ll) { |
302 | 0 | ll->address = 0; |
303 | |
|
304 | 0 | return ipv4ll_start_internal(ll, false); |
305 | 0 | } |
306 | | |
307 | 0 | static void ipv4ll_client_notify(sd_ipv4ll *ll, int event) { |
308 | 0 | assert(ll); |
309 | |
|
310 | 0 | if (ll->callback) |
311 | 0 | ll->callback(ll, event, ll->userdata); |
312 | 0 | } |
313 | | |
314 | 0 | void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata) { |
315 | 0 | sd_ipv4ll *ll = ASSERT_PTR(userdata); |
316 | 0 | IPV4LL_DONT_DESTROY(ll); |
317 | 0 | int r; |
318 | |
|
319 | 0 | assert(acd); |
320 | |
|
321 | 0 | switch (event) { |
322 | | |
323 | 0 | case SD_IPV4ACD_EVENT_STOP: |
324 | 0 | ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP); |
325 | 0 | ll->claimed_address = 0; |
326 | 0 | break; |
327 | | |
328 | 0 | case SD_IPV4ACD_EVENT_BIND: |
329 | 0 | ll->claimed_address = ll->address; |
330 | 0 | ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_BIND); |
331 | 0 | break; |
332 | | |
333 | 0 | case SD_IPV4ACD_EVENT_CONFLICT: |
334 | | /* if an address was already bound we must call up to the |
335 | | user to handle this, otherwise we just try again */ |
336 | 0 | if (ll->claimed_address != 0) { |
337 | 0 | ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_CONFLICT); |
338 | |
|
339 | 0 | ll->claimed_address = 0; |
340 | 0 | } else { |
341 | 0 | r = sd_ipv4ll_restart(ll); |
342 | 0 | if (r < 0) |
343 | 0 | goto error; |
344 | 0 | } |
345 | | |
346 | 0 | break; |
347 | | |
348 | 0 | default: |
349 | 0 | assert_not_reached(); |
350 | 0 | } |
351 | | |
352 | 0 | return; |
353 | | |
354 | 0 | error: |
355 | 0 | ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP); |
356 | 0 | } |
357 | | |
358 | 0 | static int ipv4ll_check_mac(sd_ipv4acd *acd, const struct ether_addr *mac, void *userdata) { |
359 | 0 | sd_ipv4ll *ll = ASSERT_PTR(userdata); |
360 | |
|
361 | 0 | if (ll->check_mac_callback) |
362 | 0 | return ll->check_mac_callback(ll, mac, ll->check_mac_userdata); |
363 | | |
364 | 0 | return 0; |
365 | 0 | } |