]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/sd-ipv4acd.c
tree-wide: adjust fall through comments so that gcc is happy
[thirdparty/systemd.git] / src / libsystemd-network / sd-ipv4acd.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
e3dca008
TG
2/***
3 This file is part of systemd.
4
5 Copyright (C) 2014 Axis Communications AB. All rights reserved.
6 Copyright (C) 2015 Tom Gundersen
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
22#include <arpa/inet.h>
23#include <errno.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27
07630cea
LP
28#include "sd-ipv4acd.h"
29
b5efdb8a 30#include "alloc-util.h"
07630cea 31#include "arp-util.h"
e78f9587 32#include "ether-addr-util.h"
3ffd4af2 33#include "fd-util.h"
e3dca008
TG
34#include "in-addr-util.h"
35#include "list.h"
e3dca008
TG
36#include "random-util.h"
37#include "siphash24.h"
d246e77a 38#include "string-util.h"
e3dca008
TG
39#include "util.h"
40
e3dca008 41/* Constants from the RFC */
e3f4eedb
LP
42#define PROBE_WAIT_USEC (1U * USEC_PER_SEC)
43#define PROBE_NUM 3U
44#define PROBE_MIN_USEC (1U * USEC_PER_SEC)
45#define PROBE_MAX_USEC (2U * USEC_PER_SEC)
46#define ANNOUNCE_WAIT_USEC (2U * USEC_PER_SEC)
47#define ANNOUNCE_NUM 2U
48#define ANNOUNCE_INTERVAL_USEC (2U * USEC_PER_SEC)
49#define MAX_CONFLICTS 10U
50#define RATE_LIMIT_INTERVAL_USEC (60U * USEC_PER_SEC)
51#define DEFEND_INTERVAL_USEC (10U * USEC_PER_SEC)
e3dca008 52
e3dca008
TG
53typedef enum IPv4ACDState {
54 IPV4ACD_STATE_INIT,
c9e458a4 55 IPV4ACD_STATE_STARTED,
e3dca008
TG
56 IPV4ACD_STATE_WAITING_PROBE,
57 IPV4ACD_STATE_PROBING,
58 IPV4ACD_STATE_WAITING_ANNOUNCE,
59 IPV4ACD_STATE_ANNOUNCING,
60 IPV4ACD_STATE_RUNNING,
61 _IPV4ACD_STATE_MAX,
62 _IPV4ACD_STATE_INVALID = -1
63} IPv4ACDState;
64
65struct sd_ipv4acd {
c116f526 66 unsigned n_ref;
e3dca008
TG
67
68 IPv4ACDState state;
2f8e7633 69 int ifindex;
e3dca008 70 int fd;
784cdc2d
LP
71
72 unsigned n_iteration;
73 unsigned n_conflict;
74
4dbf7b3a
LP
75 sd_event_source *receive_message_event_source;
76 sd_event_source *timer_event_source;
784cdc2d 77
e3dca008
TG
78 usec_t defend_window;
79 be32_t address;
784cdc2d 80
e3dca008
TG
81 /* External */
82 struct ether_addr mac_addr;
784cdc2d 83
e3dca008
TG
84 sd_event *event;
85 int event_priority;
45aa74c7 86 sd_ipv4acd_callback_t callback;
e3dca008
TG
87 void* userdata;
88};
89
b24ef049
LP
90#define log_ipv4acd_errno(acd, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "IPV4ACD: " fmt, ##__VA_ARGS__)
91#define log_ipv4acd(acd, fmt, ...) log_ipv4acd_errno(acd, 0, fmt, ##__VA_ARGS__)
3aacc173 92
b24ef049
LP
93static void ipv4acd_set_state(sd_ipv4acd *acd, IPv4ACDState st, bool reset_counter) {
94 assert(acd);
d246e77a
LP
95 assert(st < _IPV4ACD_STATE_MAX);
96
b24ef049
LP
97 if (st == acd->state && !reset_counter)
98 acd->n_iteration++;
d246e77a 99 else {
b24ef049
LP
100 acd->state = st;
101 acd->n_iteration = 0;
d246e77a
LP
102 }
103}
104
b24ef049
LP
105static void ipv4acd_reset(sd_ipv4acd *acd) {
106 assert(acd);
d246e77a 107
b24ef049
LP
108 acd->timer_event_source = sd_event_source_unref(acd->timer_event_source);
109 acd->receive_message_event_source = sd_event_source_unref(acd->receive_message_event_source);
d246e77a 110
b24ef049 111 acd->fd = safe_close(acd->fd);
d246e77a 112
b24ef049 113 ipv4acd_set_state(acd, IPV4ACD_STATE_INIT, true);
d246e77a
LP
114}
115
b24ef049
LP
116sd_ipv4acd *sd_ipv4acd_ref(sd_ipv4acd *acd) {
117 if (!acd)
c116f526
LP
118 return NULL;
119
b24ef049
LP
120 assert_se(acd->n_ref >= 1);
121 acd->n_ref++;
e3dca008 122
b24ef049 123 return acd;
e3dca008
TG
124}
125
b24ef049
LP
126sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *acd) {
127 if (!acd)
c116f526
LP
128 return NULL;
129
b24ef049
LP
130 assert_se(acd->n_ref >= 1);
131 acd->n_ref--;
c116f526 132
b24ef049 133 if (acd->n_ref > 0)
e3dca008
TG
134 return NULL;
135
b24ef049
LP
136 ipv4acd_reset(acd);
137 sd_ipv4acd_detach_event(acd);
e3dca008 138
6b430fdb 139 return mfree(acd);
e3dca008
TG
140}
141
e3dca008 142int sd_ipv4acd_new(sd_ipv4acd **ret) {
b24ef049 143 _cleanup_(sd_ipv4acd_unrefp) sd_ipv4acd *acd = NULL;
e3dca008
TG
144
145 assert_return(ret, -EINVAL);
146
b24ef049
LP
147 acd = new0(sd_ipv4acd, 1);
148 if (!acd)
e3dca008
TG
149 return -ENOMEM;
150
b24ef049
LP
151 acd->n_ref = 1;
152 acd->state = IPV4ACD_STATE_INIT;
153 acd->ifindex = -1;
154 acd->fd = -1;
e3dca008 155
b24ef049
LP
156 *ret = acd;
157 acd = NULL;
e3dca008
TG
158
159 return 0;
160}
161
b24ef049
LP
162static void ipv4acd_client_notify(sd_ipv4acd *acd, int event) {
163 assert(acd);
e3dca008 164
b24ef049 165 if (!acd->callback)
e095f51d
LP
166 return;
167
b24ef049 168 acd->callback(acd, event, acd->userdata);
e3dca008
TG
169}
170
b24ef049
LP
171int sd_ipv4acd_stop(sd_ipv4acd *acd) {
172 assert_return(acd, -EINVAL);
e3dca008 173
b24ef049 174 ipv4acd_reset(acd);
e3dca008 175
b24ef049 176 log_ipv4acd(acd, "STOPPED");
e3dca008 177
b24ef049 178 ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_STOP);
e3dca008
TG
179
180 return 0;
181}
182
183static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata);
184
b24ef049 185static int ipv4acd_set_next_wakeup(sd_ipv4acd *acd, usec_t usec, usec_t random_usec) {
4afd3348 186 _cleanup_(sd_event_source_unrefp) sd_event_source *timer = NULL;
e3f4eedb 187 usec_t next_timeout, time_now;
e3dca008
TG
188 int r;
189
b24ef049 190 assert(acd);
e3dca008 191
e3f4eedb 192 next_timeout = usec;
e3dca008 193
e3f4eedb
LP
194 if (random_usec > 0)
195 next_timeout += (usec_t) random_u64() % random_usec;
e3dca008 196
b24ef049 197 assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
e3dca008 198
b24ef049 199 r = sd_event_add_time(acd->event, &timer, clock_boottime_or_monotonic(), time_now + next_timeout, 0, ipv4acd_on_timeout, acd);
e3dca008
TG
200 if (r < 0)
201 return r;
202
b24ef049 203 r = sd_event_source_set_priority(timer, acd->event_priority);
e3dca008
TG
204 if (r < 0)
205 return r;
206
4dbf7b3a 207 (void) sd_event_source_set_description(timer, "ipv4acd-timer");
e3dca008 208
b24ef049
LP
209 sd_event_source_unref(acd->timer_event_source);
210 acd->timer_event_source = timer;
e3dca008
TG
211 timer = NULL;
212
213 return 0;
214}
215
b24ef049
LP
216static bool ipv4acd_arp_conflict(sd_ipv4acd *acd, struct ether_arp *arp) {
217 assert(acd);
e3dca008
TG
218 assert(arp);
219
220 /* see the BPF */
b24ef049 221 if (memcmp(arp->arp_spa, &acd->address, sizeof(acd->address)) == 0)
e3dca008
TG
222 return true;
223
224 /* the TPA matched instead of the SPA, this is not a conflict */
225 return false;
226}
227
228static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
b24ef049 229 sd_ipv4acd *acd = userdata;
e3dca008
TG
230 int r = 0;
231
b24ef049 232 assert(acd);
e3dca008 233
b24ef049 234 switch (acd->state) {
e3dca008 235
c9e458a4 236 case IPV4ACD_STATE_STARTED:
b24ef049 237 ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_PROBE, true);
e3dca008 238
b24ef049 239 if (acd->n_conflict >= MAX_CONFLICTS) {
e3f4eedb 240 char ts[FORMAT_TIMESPAN_MAX];
b24ef049 241 log_ipv4acd(acd, "Max conflicts reached, delaying by %s", format_timespan(ts, sizeof(ts), RATE_LIMIT_INTERVAL_USEC, 0));
a48fc60a 242
b24ef049 243 r = ipv4acd_set_next_wakeup(acd, RATE_LIMIT_INTERVAL_USEC, PROBE_WAIT_USEC);
e3dca008 244 if (r < 0)
ff0c5ebd 245 goto fail;
e3dca008 246 } else {
b24ef049 247 r = ipv4acd_set_next_wakeup(acd, 0, PROBE_WAIT_USEC);
e3dca008 248 if (r < 0)
ff0c5ebd 249 goto fail;
e3dca008
TG
250 }
251
252 break;
4dbf7b3a 253
e3dca008
TG
254 case IPV4ACD_STATE_WAITING_PROBE:
255 case IPV4ACD_STATE_PROBING:
256 /* Send a probe */
b24ef049 257 r = arp_send_probe(acd->fd, acd->ifindex, acd->address, &acd->mac_addr);
e3dca008 258 if (r < 0) {
b24ef049 259 log_ipv4acd_errno(acd, r, "Failed to send ARP probe: %m");
ff0c5ebd 260 goto fail;
e3dca008
TG
261 } else {
262 _cleanup_free_ char *address = NULL;
b24ef049 263 union in_addr_union addr = { .in.s_addr = acd->address };
e3dca008 264
d246e77a 265 (void) in_addr_to_string(AF_INET, &addr, &address);
b24ef049 266 log_ipv4acd(acd, "Probing %s", strna(address));
e3dca008
TG
267 }
268
b24ef049
LP
269 if (acd->n_iteration < PROBE_NUM - 2) {
270 ipv4acd_set_state(acd, IPV4ACD_STATE_PROBING, false);
e3dca008 271
b24ef049 272 r = ipv4acd_set_next_wakeup(acd, PROBE_MIN_USEC, (PROBE_MAX_USEC-PROBE_MIN_USEC));
e3dca008 273 if (r < 0)
ff0c5ebd 274 goto fail;
e3dca008 275 } else {
b24ef049 276 ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_ANNOUNCE, true);
e3dca008 277
b24ef049 278 r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_WAIT_USEC, 0);
e3dca008 279 if (r < 0)
ff0c5ebd 280 goto fail;
e3dca008
TG
281 }
282
283 break;
284
285 case IPV4ACD_STATE_ANNOUNCING:
b24ef049
LP
286 if (acd->n_iteration >= ANNOUNCE_NUM - 1) {
287 ipv4acd_set_state(acd, IPV4ACD_STATE_RUNNING, false);
e3dca008
TG
288 break;
289 }
4dbf7b3a 290
4831981d 291 _fallthrough_;
e3dca008
TG
292 case IPV4ACD_STATE_WAITING_ANNOUNCE:
293 /* Send announcement packet */
b24ef049 294 r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr);
e3dca008 295 if (r < 0) {
b24ef049 296 log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m");
ff0c5ebd 297 goto fail;
e3dca008 298 } else
b24ef049 299 log_ipv4acd(acd, "ANNOUNCE");
e3dca008 300
b24ef049 301 ipv4acd_set_state(acd, IPV4ACD_STATE_ANNOUNCING, false);
e3dca008 302
b24ef049 303 r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_INTERVAL_USEC, 0);
e3dca008 304 if (r < 0)
ff0c5ebd 305 goto fail;
e3dca008 306
b24ef049
LP
307 if (acd->n_iteration == 0) {
308 acd->n_conflict = 0;
309 ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_BIND);
e3dca008
TG
310 }
311
312 break;
4dbf7b3a 313
e3dca008
TG
314 default:
315 assert_not_reached("Invalid state.");
316 }
317
ff0c5ebd 318 return 0;
e3dca008 319
ff0c5ebd 320fail:
b24ef049 321 sd_ipv4acd_stop(acd);
ff0c5ebd 322 return 0;
e3dca008
TG
323}
324
b24ef049 325static void ipv4acd_on_conflict(sd_ipv4acd *acd) {
e3dca008 326 _cleanup_free_ char *address = NULL;
b24ef049 327 union in_addr_union addr = { .in.s_addr = acd->address };
e3dca008 328
b24ef049 329 assert(acd);
e3dca008 330
b24ef049 331 acd->n_conflict++;
e3dca008 332
d246e77a 333 (void) in_addr_to_string(AF_INET, &addr, &address);
b24ef049 334 log_ipv4acd(acd, "Conflict on %s (%u)", strna(address), acd->n_conflict);
e3dca008 335
b24ef049
LP
336 ipv4acd_reset(acd);
337 ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_CONFLICT);
e3dca008
TG
338}
339
e095f51d
LP
340static int ipv4acd_on_packet(
341 sd_event_source *s,
342 int fd,
343 uint32_t revents,
344 void *userdata) {
345
b24ef049 346 sd_ipv4acd *acd = userdata;
e3dca008 347 struct ether_arp packet;
e095f51d 348 ssize_t n;
e3dca008
TG
349 int r;
350
e095f51d 351 assert(s);
b24ef049 352 assert(acd);
e3dca008
TG
353 assert(fd >= 0);
354
e095f51d
LP
355 n = recv(fd, &packet, sizeof(struct ether_arp), 0);
356 if (n < 0) {
3742095b 357 if (IN_SET(errno, EAGAIN, EINTR))
004845d1
LP
358 return 0;
359
b24ef049 360 log_ipv4acd_errno(acd, errno, "Failed to read ARP packet: %m");
ff0c5ebd 361 goto fail;
e095f51d
LP
362 }
363 if ((size_t) n != sizeof(struct ether_arp)) {
b24ef049 364 log_ipv4acd(acd, "Ignoring too short ARP packet.");
e095f51d
LP
365 return 0;
366 }
e3dca008 367
b24ef049 368 switch (acd->state) {
e095f51d 369
e3dca008
TG
370 case IPV4ACD_STATE_ANNOUNCING:
371 case IPV4ACD_STATE_RUNNING:
e095f51d 372
b24ef049 373 if (ipv4acd_arp_conflict(acd, &packet)) {
e3dca008
TG
374 usec_t ts;
375
b24ef049 376 assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &ts) >= 0);
e3dca008
TG
377
378 /* Defend address */
b24ef049
LP
379 if (ts > acd->defend_window) {
380 acd->defend_window = ts + DEFEND_INTERVAL_USEC;
381 r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr);
e3dca008 382 if (r < 0) {
b24ef049 383 log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m");
ff0c5ebd 384 goto fail;
e3dca008 385 } else
b24ef049 386 log_ipv4acd(acd, "DEFEND");
e3dca008
TG
387
388 } else
b24ef049 389 ipv4acd_on_conflict(acd);
e3dca008 390 }
e3dca008 391 break;
e095f51d 392
e3dca008
TG
393 case IPV4ACD_STATE_WAITING_PROBE:
394 case IPV4ACD_STATE_PROBING:
395 case IPV4ACD_STATE_WAITING_ANNOUNCE:
396 /* BPF ensures this packet indicates a conflict */
b24ef049 397 ipv4acd_on_conflict(acd);
e3dca008 398 break;
e095f51d 399
e3dca008
TG
400 default:
401 assert_not_reached("Invalid state.");
402 }
403
ff0c5ebd 404 return 0;
e3dca008 405
ff0c5ebd 406fail:
b24ef049 407 sd_ipv4acd_stop(acd);
ff0c5ebd 408 return 0;
e3dca008
TG
409}
410
b24ef049
LP
411int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int ifindex) {
412 assert_return(acd, -EINVAL);
2f8e7633 413 assert_return(ifindex > 0, -EINVAL);
b24ef049 414 assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY);
e3dca008 415
b24ef049 416 acd->ifindex = ifindex;
e3dca008
TG
417
418 return 0;
419}
420
b24ef049
LP
421int sd_ipv4acd_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr) {
422 assert_return(acd, -EINVAL);
e3dca008 423 assert_return(addr, -EINVAL);
b24ef049 424 assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY);
e3dca008 425
b24ef049 426 acd->mac_addr = *addr;
e3dca008
TG
427
428 return 0;
429}
430
b24ef049
LP
431int sd_ipv4acd_detach_event(sd_ipv4acd *acd) {
432 assert_return(acd, -EINVAL);
e3dca008 433
b24ef049 434 acd->event = sd_event_unref(acd->event);
e3dca008
TG
435
436 return 0;
437}
438
b24ef049 439int sd_ipv4acd_attach_event(sd_ipv4acd *acd, sd_event *event, int64_t priority) {
e3dca008
TG
440 int r;
441
b24ef049
LP
442 assert_return(acd, -EINVAL);
443 assert_return(!acd->event, -EBUSY);
e3dca008
TG
444
445 if (event)
b24ef049 446 acd->event = sd_event_ref(event);
e3dca008 447 else {
b24ef049 448 r = sd_event_default(&acd->event);
e3dca008
TG
449 if (r < 0)
450 return r;
451 }
452
b24ef049 453 acd->event_priority = priority;
e3dca008
TG
454
455 return 0;
456}
457
b24ef049
LP
458int sd_ipv4acd_set_callback(sd_ipv4acd *acd, sd_ipv4acd_callback_t cb, void *userdata) {
459 assert_return(acd, -EINVAL);
e3dca008 460
b24ef049
LP
461 acd->callback = cb;
462 acd->userdata = userdata;
e3dca008
TG
463
464 return 0;
465}
466
b24ef049
LP
467int sd_ipv4acd_set_address(sd_ipv4acd *acd, const struct in_addr *address) {
468 assert_return(acd, -EINVAL);
e3dca008 469 assert_return(address, -EINVAL);
b24ef049 470 assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY);
e3dca008 471
b24ef049 472 acd->address = address->s_addr;
e3dca008
TG
473
474 return 0;
475}
476
b24ef049
LP
477int sd_ipv4acd_is_running(sd_ipv4acd *acd) {
478 assert_return(acd, false);
e3dca008 479
b24ef049 480 return acd->state != IPV4ACD_STATE_INIT;
e3dca008
TG
481}
482
b24ef049 483int sd_ipv4acd_start(sd_ipv4acd *acd) {
e3dca008
TG
484 int r;
485
b24ef049
LP
486 assert_return(acd, -EINVAL);
487 assert_return(acd->event, -EINVAL);
488 assert_return(acd->ifindex > 0, -EINVAL);
489 assert_return(acd->address != 0, -EINVAL);
490 assert_return(!ether_addr_is_null(&acd->mac_addr), -EINVAL);
491 assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY);
e3dca008 492
b24ef049 493 r = arp_network_bind_raw_socket(acd->ifindex, acd->address, &acd->mac_addr);
e3dca008 494 if (r < 0)
d246e77a 495 return r;
e3dca008 496
b24ef049
LP
497 safe_close(acd->fd);
498 acd->fd = r;
499 acd->defend_window = 0;
500 acd->n_conflict = 0;
e3dca008 501
b24ef049 502 r = sd_event_add_io(acd->event, &acd->receive_message_event_source, acd->fd, EPOLLIN, ipv4acd_on_packet, acd);
e3dca008 503 if (r < 0)
d246e77a 504 goto fail;
e3dca008 505
b24ef049 506 r = sd_event_source_set_priority(acd->receive_message_event_source, acd->event_priority);
e3dca008 507 if (r < 0)
d246e77a 508 goto fail;
e3dca008 509
b24ef049 510 (void) sd_event_source_set_description(acd->receive_message_event_source, "ipv4acd-receive-message");
e3dca008 511
b24ef049 512 r = ipv4acd_set_next_wakeup(acd, 0, 0);
e3dca008 513 if (r < 0)
d246e77a 514 goto fail;
e3dca008 515
b24ef049 516 ipv4acd_set_state(acd, IPV4ACD_STATE_STARTED, true);
e3dca008 517 return 0;
d246e77a
LP
518
519fail:
b24ef049 520 ipv4acd_reset(acd);
d246e77a 521 return r;
e3dca008 522}