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