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