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