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