]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/sd-lldp.c
macro: introduce DEFINE_TRIVIAL_REF_UNREF_FUNC() macro and friends
[thirdparty/systemd.git] / src / libsystemd-network / sd-lldp.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
ad1ad5c8
SS
2
3#include <arpa/inet.h>
284d1cd0 4#include <linux/sockios.h>
ad1ad5c8 5
ad1ad5c8 6#include "sd-lldp.h"
07630cea 7
b5efdb8a 8#include "alloc-util.h"
3ffd4af2 9#include "fd-util.h"
ad1ad5c8 10#include "lldp-internal.h"
34437b4f 11#include "lldp-neighbor.h"
032b27f5 12#include "lldp-network.h"
34437b4f 13#include "socket-util.h"
b553a6b1 14#include "ether-addr-util.h"
49699bac 15
34437b4f 16#define LLDP_DEFAULT_NEIGHBORS_MAX 128U
032b27f5 17
34437b4f
LP
18static void lldp_flush_neighbors(sd_lldp *lldp) {
19 sd_lldp_neighbor *n;
ad1ad5c8
SS
20
21 assert(lldp);
ad1ad5c8 22
34437b4f
LP
23 while ((n = hashmap_first(lldp->neighbor_by_id)))
24 lldp_neighbor_unlink(n);
ad1ad5c8
SS
25}
26
90dffb22
LP
27static void lldp_callback(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n) {
28 assert(lldp);
90dffb22
LP
29
30 log_lldp("Invoking callback for '%c'.", event);
31
32 if (!lldp->callback)
33 return;
34
35 lldp->callback(lldp, event, n, lldp->userdata);
36}
37
34437b4f 38static int lldp_make_space(sd_lldp *lldp, size_t extra) {
34437b4f
LP
39 usec_t t = USEC_INFINITY;
40 bool changed = false;
ad1ad5c8 41
34437b4f 42 assert(lldp);
ad1ad5c8 43
34437b4f
LP
44 /* Remove all entries that are past their TTL, and more until at least the specified number of extra entries
45 * are free. */
ad1ad5c8 46
34437b4f 47 for (;;) {
90dffb22
LP
48 _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
49
34437b4f
LP
50 n = prioq_peek(lldp->neighbor_by_expiry);
51 if (!n)
859c37b1 52 break;
859c37b1 53
90dffb22
LP
54 sd_lldp_neighbor_ref(n);
55
34437b4f
LP
56 if (hashmap_size(lldp->neighbor_by_id) > LESS_BY(lldp->neighbors_max, extra))
57 goto remove_one;
859c37b1 58
34437b4f
LP
59 if (t == USEC_INFINITY)
60 t = now(clock_boottime_or_monotonic());
859c37b1 61
34437b4f 62 if (n->until > t)
859c37b1 63 break;
859c37b1 64
34437b4f
LP
65 remove_one:
66 lldp_neighbor_unlink(n);
90dffb22 67 lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, n);
34437b4f
LP
68 changed = true;
69 }
859c37b1 70
34437b4f
LP
71 return changed;
72}
ad1ad5c8 73
90dffb22
LP
74static bool lldp_keep_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
75 assert(lldp);
76 assert(n);
77
78 /* Don't keep data with a zero TTL */
79 if (n->ttl <= 0)
80 return false;
81
82 /* Filter out data from the filter address */
83 if (!ether_addr_is_null(&lldp->filter_address) &&
84 ether_addr_equal(&lldp->filter_address, &n->source_address))
85 return false;
86
87 /* Only add if the neighbor has a capability we are interested in. Note that we also store all neighbors with
88 * no caps field set. */
89 if (n->has_capabilities &&
90 (n->enabled_capabilities & lldp->capability_mask) == 0)
91 return false;
92
93 /* Keep everything else */
94 return true;
95}
96
0513ea4e
TH
97static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor);
98
34437b4f 99static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
90dffb22
LP
100 _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *old = NULL;
101 bool keep;
34437b4f 102 int r;
ad1ad5c8 103
34437b4f
LP
104 assert(lldp);
105 assert(n);
106 assert(!n->lldp);
107
90dffb22
LP
108 keep = lldp_keep_neighbor(lldp, n);
109
34437b4f
LP
110 /* First retrieve the old entry for this MSAP */
111 old = hashmap_get(lldp->neighbor_by_id, &n->id);
112 if (old) {
90dffb22
LP
113 sd_lldp_neighbor_ref(old);
114
115 if (!keep) {
116 lldp_neighbor_unlink(old);
117 lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old);
118 return 0;
119 }
120
34437b4f
LP
121 if (lldp_neighbor_equal(n, old)) {
122 /* Is this equal, then restart the TTL counter, but don't do anyting else. */
16fed825 123 old->timestamp = n->timestamp;
0513ea4e 124 lldp_start_timer(lldp, old);
90dffb22 125 lldp_callback(lldp, SD_LLDP_EVENT_REFRESHED, old);
34437b4f 126 return 0;
ad1ad5c8 127 }
34437b4f
LP
128
129 /* Data changed, remove the old entry, and add a new one */
130 lldp_neighbor_unlink(old);
ad1ad5c8 131
90dffb22
LP
132 } else if (!keep)
133 return 0;
ad1ad5c8 134
34437b4f
LP
135 /* Then, make room for at least one new neighbor */
136 lldp_make_space(lldp, 1);
ad1ad5c8 137
34437b4f
LP
138 r = hashmap_put(lldp->neighbor_by_id, &n->id, n);
139 if (r < 0)
90dffb22 140 goto finish;
ad1ad5c8 141
34437b4f
LP
142 r = prioq_put(lldp->neighbor_by_expiry, n, &n->prioq_idx);
143 if (r < 0) {
144 assert_se(hashmap_remove(lldp->neighbor_by_id, &n->id) == n);
90dffb22 145 goto finish;
ad1ad5c8
SS
146 }
147
34437b4f 148 n->lldp = lldp;
ad1ad5c8 149
0513ea4e 150 lldp_start_timer(lldp, n);
90dffb22
LP
151 lldp_callback(lldp, old ? SD_LLDP_EVENT_UPDATED : SD_LLDP_EVENT_ADDED, n);
152
153 return 1;
154
155finish:
156 if (old)
35ad2cd7 157 lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old);
90dffb22
LP
158
159 return r;
ad1ad5c8
SS
160}
161
34437b4f
LP
162static int lldp_handle_datagram(sd_lldp *lldp, sd_lldp_neighbor *n) {
163 int r;
ad1ad5c8 164
34437b4f
LP
165 assert(lldp);
166 assert(n);
ad1ad5c8 167
34437b4f
LP
168 r = lldp_neighbor_parse(n);
169 if (r == -EBADMSG) /* Ignore bad messages */
170 return 0;
171 if (r < 0)
172 return r;
ad1ad5c8 173
34437b4f
LP
174 r = lldp_add_neighbor(lldp, n);
175 if (r < 0) {
176 log_lldp_errno(r, "Failed to add datagram. Ignoring.");
177 return 0;
ad1ad5c8 178 }
ad1ad5c8 179
34437b4f 180 log_lldp("Successfully processed LLDP datagram.");
34437b4f 181 return 0;
ad1ad5c8
SS
182}
183
34437b4f
LP
184static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
185 _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
186 ssize_t space, length;
187 sd_lldp *lldp = userdata;
16fed825 188 struct timespec ts;
49699bac 189
34437b4f 190 assert(fd >= 0);
49699bac 191 assert(lldp);
49699bac 192
34437b4f
LP
193 space = next_datagram_size_fd(fd);
194 if (space < 0)
195 return log_lldp_errno(space, "Failed to determine datagram size to read: %m");
49699bac 196
34437b4f
LP
197 n = lldp_neighbor_new(space);
198 if (!n)
199 return -ENOMEM;
49699bac 200
34437b4f 201 length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT);
f3315c58 202 if (length < 0) {
4c701096 203 if (IN_SET(errno, EAGAIN, EINTR))
f3315c58
LP
204 return 0;
205
34437b4f 206 return log_lldp_errno(errno, "Failed to read LLDP datagram: %m");
f3315c58 207 }
dacd6cee 208
34437b4f
LP
209 if ((size_t) length != n->raw_size) {
210 log_lldp("Packet size mismatch.");
211 return -EINVAL;
212 }
49699bac 213
16fed825
LP
214 /* Try to get the timestamp of this packet if it is known */
215 if (ioctl(fd, SIOCGSTAMPNS, &ts) >= 0)
216 triple_timestamp_from_realtime(&n->timestamp, timespec_load(&ts));
217 else
218 triple_timestamp_get(&n->timestamp);
219
34437b4f 220 return lldp_handle_datagram(lldp, n);
49699bac
SS
221}
222
fc6a313b
LP
223static void lldp_reset(sd_lldp *lldp) {
224 assert(lldp);
225
226 lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source);
227 lldp->io_event_source = sd_event_source_unref(lldp->io_event_source);
228 lldp->fd = safe_close(lldp->fd);
229}
230
34437b4f 231_public_ int sd_lldp_start(sd_lldp *lldp) {
ad1ad5c8
SS
232 int r;
233
234 assert_return(lldp, -EINVAL);
fc6a313b
LP
235 assert_return(lldp->event, -EINVAL);
236 assert_return(lldp->ifindex > 0, -EINVAL);
ad1ad5c8 237
032b27f5
LP
238 if (lldp->fd >= 0)
239 return 0;
ad1ad5c8 240
34437b4f 241 assert(!lldp->io_event_source);
49699bac 242
032b27f5
LP
243 lldp->fd = lldp_network_bind_raw_socket(lldp->ifindex);
244 if (lldp->fd < 0)
245 return lldp->fd;
246
fc6a313b
LP
247 r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp);
248 if (r < 0)
249 goto fail;
032b27f5 250
fc6a313b
LP
251 r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority);
252 if (r < 0)
253 goto fail;
032b27f5 254
fc6a313b 255 (void) sd_event_source_set_description(lldp->io_event_source, "lldp-io");
ad1ad5c8 256
fc6a313b 257 log_lldp("Started LLDP client");
032b27f5
LP
258 return 1;
259
260fail:
fc6a313b 261 lldp_reset(lldp);
032b27f5 262 return r;
ad1ad5c8
SS
263}
264
34437b4f 265_public_ int sd_lldp_stop(sd_lldp *lldp) {
ad1ad5c8 266 assert_return(lldp, -EINVAL);
ad1ad5c8 267
032b27f5
LP
268 if (lldp->fd < 0)
269 return 0;
ad1ad5c8 270
fc6a313b 271 log_lldp("Stopping LLDP client");
ad1ad5c8 272
fc6a313b 273 lldp_reset(lldp);
34437b4f 274 lldp_flush_neighbors(lldp);
ad1ad5c8 275
032b27f5 276 return 1;
ad1ad5c8
SS
277}
278
34437b4f 279_public_ int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority) {
ad1ad5c8
SS
280 int r;
281
282 assert_return(lldp, -EINVAL);
032b27f5
LP
283 assert_return(lldp->fd < 0, -EBUSY);
284 assert_return(!lldp->event, -EBUSY);
ad1ad5c8
SS
285
286 if (event)
032b27f5 287 lldp->event = sd_event_ref(event);
ad1ad5c8 288 else {
032b27f5 289 r = sd_event_default(&lldp->event);
ad1ad5c8
SS
290 if (r < 0)
291 return r;
292 }
293
032b27f5 294 lldp->event_priority = priority;
ad1ad5c8
SS
295
296 return 0;
297}
298
34437b4f 299_public_ int sd_lldp_detach_event(sd_lldp *lldp) {
ad1ad5c8
SS
300
301 assert_return(lldp, -EINVAL);
032b27f5 302 assert_return(lldp->fd < 0, -EBUSY);
ad1ad5c8 303
032b27f5 304 lldp->event = sd_event_unref(lldp->event);
ad1ad5c8
SS
305 return 0;
306}
307
3db2ec56
LP
308_public_ sd_event* sd_lldp_get_event(sd_lldp *lldp) {
309 assert_return(lldp, NULL);
310
311 return lldp->event;
312}
313
34437b4f 314_public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata) {
49699bac
SS
315 assert_return(lldp, -EINVAL);
316
ccf86354 317 lldp->callback = cb;
49699bac
SS
318 lldp->userdata = userdata;
319
320 return 0;
321}
322
fc6a313b
LP
323_public_ int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex) {
324 assert_return(lldp, -EINVAL);
325 assert_return(ifindex > 0, -EINVAL);
326 assert_return(lldp->fd < 0, -EBUSY);
327
328 lldp->ifindex = ifindex;
329 return 0;
330}
331
332_public_ sd_lldp* sd_lldp_ref(sd_lldp *lldp) {
333
334 if (!lldp)
335 return NULL;
336
337 assert(lldp->n_ref > 0);
338 lldp->n_ref++;
339
340 return lldp;
341}
342
34437b4f 343_public_ sd_lldp* sd_lldp_unref(sd_lldp *lldp) {
ad1ad5c8
SS
344
345 if (!lldp)
4afd3348 346 return NULL;
ad1ad5c8 347
fc6a313b
LP
348 assert(lldp->n_ref > 0);
349 lldp->n_ref --;
350
351 if (lldp->n_ref > 0)
352 return NULL;
353
354 lldp_reset(lldp);
355 sd_lldp_detach_event(lldp);
34437b4f 356 lldp_flush_neighbors(lldp);
ad1ad5c8 357
34437b4f
LP
358 hashmap_free(lldp->neighbor_by_id);
359 prioq_free(lldp->neighbor_by_expiry);
6b430fdb 360 return mfree(lldp);
ad1ad5c8
SS
361}
362
fc6a313b 363_public_ int sd_lldp_new(sd_lldp **ret) {
4afd3348 364 _cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL;
ad1ad5c8
SS
365 int r;
366
367 assert_return(ret, -EINVAL);
ad1ad5c8
SS
368
369 lldp = new0(sd_lldp, 1);
370 if (!lldp)
371 return -ENOMEM;
372
fc6a313b 373 lldp->n_ref = 1;
032b27f5 374 lldp->fd = -1;
34437b4f
LP
375 lldp->neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX;
376 lldp->capability_mask = (uint16_t) -1;
ad1ad5c8 377
34437b4f
LP
378 lldp->neighbor_by_id = hashmap_new(&lldp_neighbor_id_hash_ops);
379 if (!lldp->neighbor_by_id)
ad1ad5c8
SS
380 return -ENOMEM;
381
34437b4f 382 r = prioq_ensure_allocated(&lldp->neighbor_by_expiry, lldp_neighbor_prioq_compare_func);
ad1ad5c8
SS
383 if (r < 0)
384 return r;
385
1cc6c93a 386 *ret = TAKE_PTR(lldp);
ad1ad5c8
SS
387
388 return 0;
389}
7434883c 390
34437b4f
LP
391static int neighbor_compare_func(const void *a, const void *b) {
392 const sd_lldp_neighbor * const*x = a, * const *y = b;
7434883c 393
34437b4f
LP
394 return lldp_neighbor_id_hash_ops.compare(&(*x)->id, &(*y)->id);
395}
396
34437b4f
LP
397static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) {
398 sd_lldp *lldp = userdata;
bb1d9534 399 int r;
34437b4f
LP
400
401 r = lldp_make_space(lldp, 0);
402 if (r < 0)
403 return log_lldp_errno(r, "Failed to make space: %m");
404
bb1d9534
ZJS
405 r = lldp_start_timer(lldp, NULL);
406 if (r < 0)
407 return log_lldp_errno(r, "Failed to restart timer: %m");
34437b4f 408
34437b4f
LP
409 return 0;
410}
7434883c 411
0513ea4e 412static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor) {
34437b4f
LP
413 sd_lldp_neighbor *n;
414 int r;
415
416 assert(lldp);
417
0513ea4e
TH
418 if (neighbor)
419 lldp_neighbor_start_ttl(neighbor);
420
34437b4f
LP
421 n = prioq_peek(lldp->neighbor_by_expiry);
422 if (!n) {
423
424 if (lldp->timer_event_source)
425 return sd_event_source_set_enabled(lldp->timer_event_source, SD_EVENT_OFF);
426
427 return 0;
428 }
429
430 if (lldp->timer_event_source) {
431 r = sd_event_source_set_time(lldp->timer_event_source, n->until);
432 if (r < 0)
433 return r;
434
435 return sd_event_source_set_enabled(lldp->timer_event_source, SD_EVENT_ONESHOT);
7434883c
BG
436 }
437
34437b4f
LP
438 if (!lldp->event)
439 return 0;
440
441 r = sd_event_add_time(lldp->event, &lldp->timer_event_source, clock_boottime_or_monotonic(), n->until, 0, on_timer_event, lldp);
442 if (r < 0)
443 return r;
444
445 r = sd_event_source_set_priority(lldp->timer_event_source, lldp->event_priority);
446 if (r < 0)
447 return r;
448
449 (void) sd_event_source_set_description(lldp->timer_event_source, "lldp-timer");
450 return 0;
451}
452
453_public_ int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***ret) {
454 sd_lldp_neighbor **l = NULL, *n;
455 Iterator i;
456 int k = 0, r;
457
458 assert_return(lldp, -EINVAL);
459 assert_return(ret, -EINVAL);
460
34437b4f
LP
461 if (hashmap_isempty(lldp->neighbor_by_id)) { /* Special shortcut */
462 *ret = NULL;
7434883c
BG
463 return 0;
464 }
465
34437b4f
LP
466 l = new0(sd_lldp_neighbor*, hashmap_size(lldp->neighbor_by_id));
467 if (!l)
7434883c
BG
468 return -ENOMEM;
469
0513ea4e 470 r = lldp_start_timer(lldp, NULL);
34437b4f
LP
471 if (r < 0) {
472 free(l);
473 return r;
7434883c
BG
474 }
475
34437b4f
LP
476 HASHMAP_FOREACH(n, lldp->neighbor_by_id, i)
477 l[k++] = sd_lldp_neighbor_ref(n);
478
479 assert((size_t) k == hashmap_size(lldp->neighbor_by_id));
480
481 /* Return things in a stable order */
482 qsort(l, k, sizeof(sd_lldp_neighbor*), neighbor_compare_func);
483 *ret = l;
484
485 return k;
486}
487
488_public_ int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t m) {
489 assert_return(lldp, -EINVAL);
490 assert_return(m <= 0, -EINVAL);
491
492 lldp->neighbors_max = m;
493 lldp_make_space(lldp, 0);
494
495 return 0;
496}
497
498_public_ int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask) {
499 assert_return(lldp, -EINVAL);
500 assert_return(mask != 0, -EINVAL);
501
502 lldp->capability_mask = mask;
503
504 return 0;
7434883c 505}
b553a6b1
LP
506
507_public_ int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *addr) {
508 assert_return(lldp, -EINVAL);
509
510 /* In order to deal nicely with bridges that send back our own packets, allow one address to be filtered, so
511 * that our own can be filtered out here. */
512
a1fb61b0
LP
513 if (addr)
514 lldp->filter_address = *addr;
515 else
b553a6b1 516 zero(lldp->filter_address);
b553a6b1 517
b553a6b1
LP
518 return 0;
519}