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