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