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